牛客Leetcode刷题

出现的语法错误或者需记住的点:

C++:

1、不是指针用“.”而不是“->”。

2、且用"&&"而不是“&”。

3、set的查找:s.find(sth) != s.end()

4、string取子串:s.substr(begin, len),注意第二个参数是截取子串的长度,不是结束位置!

5、string的长度:s.length(),注意有括号。

6、queue的初始化方式可以是:queue<int> q({2,3});

7、用new生成对象返回的是指针,如:Node* node = new Node();

8、map采用红黑树实现,unordered_map采用哈希表实现。

判断一个key是否存在时,只能用mp.find(sth) == mp.end()!!!不要用mp[sth] == NULL判断。

但是需要注意的是,会对value默认初始化,比如map<int, int> mp中没有插入key = 2的元素,但是访问mp[2]会得到0。

只要访问过mp[sth],比如int a = mp[sth],那么之后调用mp.find(sth)就不等于 mp.end()!!!,也就是说mp对sth赋了初始值。

9、发生段错误可能是:没有return结果

10、vector大小:v.size(),好像就string是length()?

11、stack不可以用foreach的形式遍历(没有begin函数的不行)。

12、map的列表初始化方法:

1 map<char, char> mp = {{'(', ')'}, {'[', ']'}, {'{', '}'}};

13、 关于双向链表list,pair,迭代器,请见另一博客的:12-14

14、string的比较,可用"==", ">"等。

15、vector插入:v.insert(it, sth),其中it为迭代器,在it插入。如v.insert(v.begin(), 1),即在v的开始插入1.

16、priority_queue:C++优先队列

Python:

1、字符串相等的比较用"=="即可。"sth".join(arr)可以方便的用sth将字符串数组分隔开,如'\'.join(paths)。

2、没有 else if !!!,应用elif。

3、列表的pop()函数可以删除最后一个值。

4、random.randint(a, b):a到b闭区间的随机整数值。

5、类内方法的调用:直接在给定的方法内写一个方法就行,且参数不需要self

6、没有 && 、!、||,用and、not、or来替代!!!

7、但是 != 可以有。

8、访问list a 的最后一个元素可用 a[-1],list的pop会删除最后一个元素,并且将其返回!!(和c++的pop不同)。

9、定义和访问类的成员变量都要加上self!!

10、字符串转int: int(str) ;int转字符串: str(integer) 。

11、注意 "/" 和 "//"!!!整型用 "/"会得到浮点数结果。

12、注意 list 和 tuple 的加法都是拼接,不是按照元素相加!!!按元素加得用for循环!!!

13、字典是 dict,不是map!!!另外  dic = {} 比 dic = dict{} 更高效。

dic的访问key对应的value: dic.get(key, -1) ,这样若key不存在于dic,则得到-1。

14、学习直接通过列表进行for循环的简洁代码书写方式:

1 [dic.get(n, -1) for n in nums1]

比下面的更简洁。。。:

1 res = [-1]*len(nums1)
2 for i, n in enumerate(nums1):
3     res[i] = dic.get(n, -1)

 15、(用heapq包实现,默认小顶堆)

先定义一个列表,如 min_q = [];插入新数值时:heapq.heappush(min_q, val);删除堆顶元素并返回:heapq.heappop(min_q);只访问堆顶元素:min_q[0]。

大顶堆:max_q = [];插入新数值时:heapq.heappush(max_q , [-val, val]),这样会按照元组的第一个值排序;删除堆顶元素并返回:heapq.heappop(max_q )[1];只访问堆顶元素:max_q [0]。

16、ATTENTION !!!

定义一个包含 N 个空列表的列表:[ [] ] * N 是不对的!!!,这样所有子列表都是同一个对象!!!即对其中一个更改,其它也会同样变化。

正确的定义方法是:[ [ ] for _ in range(N)]。

17、deque的使用:

导入方式为:from collections import deque。注意是 deque,不是 dequeue!!!

右边增加元素:q.append(val);左边增加元素:q.appendleft(val);右边pop:val = q.pop();左边pop:val = q.popleft()。

判断是否空: 直接 while q: ... 即可。即可像list那样,直接转为 bool 值,如 bool([ ]) 为 False.

18、set:

添加元素:用 add 函数。

题目简介:

1、二叉树最小深度(Minumum depth of binary tree)

有深度优先、层次遍历两种做法。

2、链表常数空间复杂度,nlogn时间复杂度排序(insertion sort list)

递归归并排序不满足空间复杂度要求,因此要采用自底向上的归并排序。

3、链表插入排序(sort list)

定义一个新的链表头,对于原链表中每一个节点,插入到新的链表中(新的链表从前往后查找插入位置)。

 4、word-break

动态规划,判断一个字符串是否可以划分成,由给定的单词集组成的序列。该题可以和零钱兑换对照

定义一个数组dp,dp[i]表示字符串的前i个字母(即下标从0到i-1)是否可划分,当存在j<i,dp[j]=true且从j+1个到第i个字母组成的子序列(s.substr(j, i-j+1))存在于单词集时,dp[i]=true。

5、clone-graph

给定图的一个节点的指针,对该图进行深复制。

可以采用BFS或者DFS,注意,图的BFS或DFS和树不同,是需要判断节点是否访问过的!!!

这里用一个unordered_map<Node*, Node*> mp判读一个节点是否访问过。这里一方面可以判断某节点是否访问过,另一方面还可以找到原图中的节点,对应的新图中的节点。

该题用的时间较长(大概3、4个小时),忘记了BFS或者DFS需要标记节点是否访问过,另外使用了两个queue完成BFS,中间存在问题。

6、反转链表

两种方法:

第一种是递归,将后面的链表翻转,返回的结果再和当前节点相连。

第二种是用3个指针遍历链表:pPrev,pCur,pNext。还需要一个pReversedHead记录反转链表的头节点。

7、Two sum

数组中找的两个数之和等于target。方法:哈希表保存。

8、gas-station

环形的公路上有n个加油站,给定各个加油站的油量,以及每两个加油站间的消耗,初始加油站为空,假设汽车可储存油量无限,求唯一的加油站,以之为起点,可走完一圈。

两个下标,start和end,初始start=size-1,即最后一个加油站;end=0,即第一个加油站;

令sum为从start到end剩余的油量,若sum小于0,则说明从start开始无法走完,令start减一;否则说明start可以走到end,令end++。

最终start=end,退出循环,若此时sum>=0,说明start为可行起点;否则返回-1。(题目假设可行起点唯一)。

9、Validate Binary Search Tree

给定一个二叉搜索树的根节点,判断其是否为有效的二叉搜索树,不能有重复的值。

我最初的解法是递归中序遍历二叉树,并将各节点的值放入vector中,然后判断。

解法1:code1

递归中序遍历,并用一个全局的布尔值valid记录是否有效,以及一个全局的整型值prev记录上一个节点的值,从而方便和当前节点比较。

解法2:code2

非递归中序遍历。

10、最大子序列

解法1:code1

给定一个数组,找出其和最大的连续子序列。

用一个max记录最大和,sum初始为0,逐次加上数组中的元素,

若sum <= 0,则令sum=0,因为其对后续的子序列无帮助。

解法2code2

用分治法,取数组的mid,那么最大序列可能只在左部分取到,也可能只在右部分取到,这两种情况可用递归实现。

另外就是可能包含mid元素,那么从mid向左遍历,得到最大值max3;从mid+1向右遍历,得到最大值max4;则包含mid的最大子序列为max3+max4。

取以上3种情况最大值,记为当前序列的最大连续子序列。

11、无重复字符的最长子串:code

使用一个map记录字符最近出现的下标,一个变量left记录当前未重复子串的最左边下标。

访问字符串的第i个字符时,先判断该字符是否出现过,若出现过就令left = max(left, 出现的idx+1),这样就保证了当前子串不重复。

12、买卖股票的最佳时机

给定一支股票几天的价格:

1、若只能买卖一次,求最大利润:code

2、可买卖多次,但下一次买必须在上一次卖出后进行:code

这种情况下,用一个变量min记录上次卖出后遇到的股票的最低价,当第二天股票价格会跌时,卖出当天的股票.

比如股票3天价格为:1、5、6。这种递增情况中间卖了再买,和最后卖掉利润是一样的。

若股票4天价格为:1、5、3、6。中间有下降,那么下降之前卖掉可获得更多利润:5-1 + 6-3 = 7.

13、3 sum:code

请见:3 sum

14、两数相加:code

用链表逆序表示两个数,返回其相加的结果的链表(也是逆序)。

用一个prev变量记录上一位的进位。最后注意若最后一个进位不为0,需要将进位加上。

15、之字形遍历二叉树

方法1:code1

常规的层序遍历方法,即用queue实现,需要反向遍历时,将该层的结果的vector reverse(我用了一个栈辅助reverse,差不多)。

方法2:code2

用两个栈完成之字形遍历,正向时访问栈1,将子节点放到栈2;

反向时访问栈2,将子节点放到栈1(注意要先放右子节点,再放左子节点)。

16、Valid Parentheses:code

判断是否是合法的括号序列。

若是左括号则入栈;

若当前为右括号且栈空或者与栈顶括号不匹配,返回false。

所有字符遍历完之后,若栈不空,返回false。

17、合并K个有序链表:code

使用优先队列(最小堆),将k个链表的头节点加入优先队列。每次删除堆顶节点,将其加入结果链表,

若该节点还有后继节点,将后继节点加入最小堆。

注意优先队列的用法。即需要定义结构体,里面定义的函数返回的布尔值,和排序时的理解相反。等等。详见优先队列

18、包含min函数的栈(最小栈):code

使用2个栈s1,s2。s1和普通栈一样;s2只有当要加入的值小于等于栈顶值时,才入栈,即s2是从栈底到栈顶非严格递减的。

出栈时,只有s1和s2栈顶元素相同时,s1和s2都出栈。否则只有s1出栈。

19、LRU:

方法1:code

实现最近最少使用缓存机制。用一个双向链表以及一个map实现。

双向链表直接使用c++的list实现,每一个元素是一个pair,存储(key, value)。

map的key即pair的key,value是list<pair<int, int>>::iterator 迭代器,可以指向双向链表中某个节点。链表的第一个节点为最近使用的节点。

put操作,先通过map判断cache中是否存在该key,若不存在,就判断cache是否已满,满了就删除cache中最后一个元素,并删除map中对应的元素(不要忘记这点)。

若存在,就先通过map得到的,指向对应元素的迭代器将其删除。最后在cache的第一个节点插入新的pair。

get操作,若不存在该key就返回-1;否则通过map找到对应的元素的迭代器,将其从list中删除,并插入到第一个节点。返回其value值。

方法2:

自己实现双向链表(未完待续)。

20、k个一组翻转链表:code

用递归的方法,先判断是否满k个节点,不满则直接返回head,

否则翻转k个节点,令head->next = 翻转后续的节点,最后返回当前的头节点。

21、验证栈序列:code

python3 version:code2

给定栈的入栈、出栈序列,判断其是否有效。

使用真实的栈进行模拟。用idx记录访问到出栈序列第几个元素,按照入栈序列进行入栈,

当某元素等于出栈序列当前元素且栈非空时,不断出栈。

最后若栈空,则有效,否则无效。

22、简化路径:code

给定Unix形式的路径,里面可能包含“..”或者“.”,以及多个连续"/"等情况,将其简化。

由于c++没有split函数,因而用python3完成。用一个栈(list即可)记录有效的目录。

对于split好的字符串数组,若某一字符串为空或者“.”,continue;若是“..”,则在栈非空的情况下将最后一个目录pop;其余情况入栈。

最后将栈中的元素拼接,这里要注意栈空的情况(此时应返回"/"),可用'/' + '/'.join(paths)实现。

23、岛屿的最大面积:code

dfs即可。

24、加一code

用数组表示的整数加一。我开始用一个栈来将结果从右到左逐渐入栈,最后再逐一放到vector。

25、balanced-binary-tree:code

判断一棵树是否为平衡二叉树,即是否所有节点左右子树的高度差不大于1.

用递归的后序遍历即可。

26、single number:code

一个数组里只有一个数出现一次,其余出现2次,找出出现一次的。

用异或。

27、single number 2:code

一个数组里只有一个数出现一次,其余出现3次,找出出现一次的。

对于32位中的每一位,计算该位总共出现的次数,取第i位的方法为:n >> i & 1,

然后将次数求余3,得到结果的这一位。将结果的第i位赋值的方法:res |= (cnt % 3 << i)。

28、链表中环的入口节点:

解法1:code

用一个set记录出现过的节点指针,发现的第一个重复的指针即为链表中环的入口节点。

解法2:code

快慢指针。先找到快慢指针在环中相遇的地方,然后两个指针分别从相遇节点和头节点开始,每次走一步,相遇的节点即环的入口节点。

这是因为,假设头节点到入口节点的距离为a,入口节点到相遇节点的距离为b,相遇节点到入口节点的距离为c,那么slow走的距离为a+b,fast走的距离为a+k(b+c)+b。

而fast走的距离为slow的2倍,即2(a+b)=a+k(b+c)+b,可得:a=(k-1)(b+c)+c,由此可得到上述结论。

29、礼物的最大价值:code

dp。设矩阵的shape为(m, n),在本地直接计算不太合适,

但是也不需要新建一个大小为(m, n)的数组,可以建一个大小为n的数组,然后逐行计算即可。

30、复原IP地址:code

给定只有数字的字符串表示的IP地址,即中间没有“.”隔开,求所有可能 的IP地址。

用深度优先搜索的方法,dfs函数的参数包括已形成的部分IP地址,未访问的字符串,已包含点的个数,以及存储正确结果的vector。

初始情况下,已形成的部分IP地址为"",即空,每次添加一个点,以及一个数字。需要判断该数字是否有效。(<=255且长度大于1的情况下首字母不能是0

当添加够四个点,但还剩余字符串时,就return,否则加入结果vector。

31、搜索旋转排序数组:无重复元素

递增数组的左边一部分被移至右边,在此数组中查找目标值。

解法一:code1

先查找数组的临界值,即数组中最小的元素。请见:

然后在两部分排好序的数组分别查找目标值。

解法二:code2

当target=nums[mid],返回mid;当nums[mid]大于等于left时,说明left到mid是连续的,这种情况下,当target>=nums[left]且target<nums[mid],right = mid - 1,否则left = mid+1;

否则当nums[mid]小于left时,情况类似。

有重复元素的情况:code

这种情况下,当nums[mid] == nums[left]时,无法缩小区间,例如[1,1,1,0,1]和[1,0,1,1,1],一个mid在左边,一个mid在右边,此时让left++就好。

32、旋转排序数组的最小值:

无重复元素:code

当nums[left]<=nums[right]时,直接返回nums[left]。否则:

nums[mid] >= nums[left]时,这时nums[mid]肯定不是最小值,因此让left = mid + 1;否则让right=mid;

有重复元素:code

当nums[mid] == nums[left]时,无法缩小区间,逐一搜索left至right的值。

33、链表的中间节点:code

用快慢指针,slow刚好指向中间节点。

34、朋友圈

给定一个邻接矩阵,求图的连通分量个数。

方法1:code1

用DFS实现,这里注意,图的节点个数是N,不是 N * N(开始我按照N * N个节点,上下左右为相邻节点,完全是错误的)。

邻接矩阵中,图的节点个数是N,当M[i][j]为1时,i 和 j 相邻。

方法2:code2

Union-Find。

35、接雨水:code

给定一个数组,每个元素代表该位置柱子的高度,求可以储存的雨水的体积。

用left数组记录到每个位置的左边位置为止,最高的高度;right数组记录到每个位置的右边位置为止,最高的高度。

则每个位置可存储的最多雨水为max(0, min(left[i], right[i]) - height[i])。

36、用栈实现队列

方法1:

用两个栈,栈顶为队首,push时借助栈2,将栈1元素逐一push到栈2,将新元素push到栈2,最后将栈2元素逐一push回栈1。这样push的复杂度为O(n)。

方法2:code

用两个栈,栈1的栈顶为队尾。push时直接push到栈1(这里若push前栈空则用front变量,记录栈1的队首)。

pop时,若栈2空,则将栈1的元素push至栈2,然后直接返回栈2的栈顶,即当前队列的队首。

取队首元素时,若栈2非空,返回栈2栈顶元素。否则返回front变量,即栈1的队首。

当且仅当栈1、栈2均为空时,队空。

37、奇偶链表:code

用两个链表odd和even,分别记录奇数位置的节点,和偶数位置的节点。最后将odd的结尾和even的头部相连。

38、字符串相加:python3

从后往前遍历两个字符串,记录进位。

39、用rand7()实现rand10():code

rand7函数生成1-7的随机数,通过rand7函数实现rand10函数。

用rand7函数生成两个随机数a, b,以a作为行数,b作为列数,可生成1-49的随机数n,

当n小于等于40时,求余10(准确说是-1求余10再加1,考虑10的倍数的情况),则得到rand10的随机数。

否则重来。

或者用 n - 10得到1-9的随机数,然后再生成一个rand7的数,可生成1-63的随机数n,依次类推。直到生成1.

40、剧情触发时间:code

有三种元素,给定每天每种元素的增量(非负),判断一个剧情什么时候会被触发(即一个三种元素的要求都被满足的最小天数)。

将每天的增量累计,得到一个递增数组,然后对每个要求req在递增数组中进行二分查找,只有当某天的三种元素的数量都大于等于req,返回则这一天大于等于req。

41、下一个更大元素:

数组nums1是数组nums2的子集,都无重复元素。求nums1的每个元素在nums2中对应元素位置之后的,第一个大于该元素的值。不存在则为-1。

方法1:code1

将数组nums2中的元素对应的下标用字典存储,那么对于nums1中的一个元素就可以从字典存储的位置开始,找第一个大于该元素的值。

方法2:code2

对nums2中的每个元素预先找到其位置后面的第一个大于该元素的值。

使用一个stack s,栈顶到栈底(即数组从后往前)的元素值递增。

从前往后遍历nums2的元素,对于一个元素n,若栈顶的元素大于n,则n也入栈;

否则栈顶元素的下一个值就是n。然后出栈,若新的栈顶元素也小于n,则新栈顶元素的下一个值就是n。直到栈顶元素大于n,n入栈。

这样就对nums2中的每个元素预先找到了,其位置后面的第一个大于该元素的值。

42、柠檬水找零:code

柠檬水卖5元,顾客排队支付,付5、10、20三种面额,开始没有钱,判断是否能为每位顾客找零。

用n_5和n_10记录5元和10元的个数,收到20元时先看能不能找5和10块,不能再看是否能找3个5块。

43、长度最小的子数组(非负):

给定一个数组nums,大小为N,找出满足和大于等于 s 的最短连续字数组的长度。

方法1:code1

定义一个add_up数组(大小  N+1),对nums数组的元素进行累计,第一个元素是0。

然后遍历子数组的左端点元素 left,用二分查找确定,满足和大于等于 s 的最小右端点。

时间复杂度:o(nlogn)

方法2:code2

双指针(滑动窗口)。

子数组左端点初始化为0,对右端点进行遍历,

子数组增加一个元素之后,判断和是否大于等于 s,

若大于等于s,那么以 left 为起点的子数组的最小右边界已确定,可将left加一,对新的子数组进行判断。

可这么理解:若 a, b, c子数组之和还小于s, 而a, b, c, d 子数组之和大于s,那么以a起始的子数组已解决。而以b为起始的子数组可能的右起点只能从 d 开始,因为 b+c < a+b+c < s。

时间复杂度为o(n),因为每个元素最多被左指针访问一次,最多被右指针访问一次。

44、矩阵置零:

方法1:code1

遍历矩阵,用一个row数组和一个col数组分别记录出现0的行数和列数。

再遍历矩阵,若当前位置的行数存在于row,或列数存在于col,将其置零。

时间复杂度:O(m*n),空间复杂度:O(m+n)

方法2:code2

为了减少空间复杂度,用矩阵的第一列和第一行的元素分别表示该列或该行是否有0.

定义两个布尔变量row, col。分别遍历矩阵的第一行,第一列,若有0,则将row或col赋值为true.

遍历矩阵的除去第一行和第一列的元素,若matrix[i][j] = 0,则将matrix[0][j]赋值为0,将matrix[i][0]赋值为0.

再遍历矩阵的除去第一行和第一列的元素,若matrix[i][0] = 0 或 matrix[0][j] = 0,则将matrix[i][j] 赋值为0.

最后用之前的row和col布尔变量,若第一行或第一列有零,将那一行或那一列赋值为0.

时间复杂度:O(m*n),空间复杂度:O(1).

45、删除字符串中所有相邻重复元素: code

若两个相邻字符相同,则这两个字符都删掉。若删除后的相邻字符仍相等,则继续删除。如 abbaca 最后的结果是 ca。

用一个栈逐个记录字符,若当前字符等于栈顶的字符,则栈顶字符出栈;否则当前字符入栈。

最后用  ''.join(stack) 将栈数组转换为字符串。

46、删除字符串中所有相邻重复元素 2 : code

这次要求删除k个重复的元素。

还是用栈逐个记录字符。此外这次需要用一个变量 cnt 计数。

当 cnt 等于 k 时,将栈中的 k 个元素出栈。

这里需要注意,还得有一个 cnt 栈,因为当出栈 k 个元素之后,cnt 需要从上个重复元素的个数开始计数。

cnt栈记录各个字符的重复次数(当字符变化时入栈上个字符的个数)。当出栈 k 个元素后,令 cnt 等于 cnt栈的栈顶,cnt栈出栈。

47、删除排序数组中的重复项:code

在原地删除,不能包含重复元素,最后返回新数组的长度。

用一个下标 idx 表示新数组的结尾,idx初始化为 -1,遍历数组中的元素,若 idx < 0 或者 当前元素不等于idx位置的元素,idx += 1,并在idx赋值当前元素。

48、删除排序数组中的重复项 2 :code

可包含至多两个重复元素。(有点绕

用一个下标 idx 表示新数组的结尾,idx初始化为 -1,遍历数组中的元素,若 idx < 0 或者 当前元素不等于idx位置的元素 或者 cnt < 2,idx += 1,并在idx赋值当前元素。

用一个 cnt 计数(表示idx位置的元素是第几个重复)。在满足上述的条件时(即idx要加一,添加新元素时),若当前元素等于idx位置的元素 且 cnt < 2,cnt += 1,否则 cnt = 1。

49、数据流的中位数:

实现两个函数:插入新数值;得到中位数。

方法1:(超出时间限制)code1

用二分查找的插入排序。首先用二分查找的方法找到新数值插入的位置。然后将该位置右边的元素右移,插入新数值。

插入的时间复杂度:查找是O(logN),移动是O(N),因此一次插入是O(N)。

方法2:code2

前一半数值用大顶堆存储,后一半数值用小顶堆存储。

插入新数值时,先插入数值较小的大顶堆,插入后将大顶堆堆顶 pop 得到 val,再将 val 插入小顶堆,这样就完成了堆的调整。

当插入后的数值个数为奇数时,让大顶堆堆顶为中位数,即大顶堆比小顶堆多一个元素,因此需要将小顶堆堆顶 pop,将其插入大顶堆。

查找时,若元素个数为奇数,则返回大顶堆堆顶;否则返回两个堆堆顶元素的平均数。

50、课程表:code

给定图的节点数,以及所有边,判断是否是拓扑图。

拓扑排序。首先建图(邻接表),建图时记录所有节点的入度。

用一个队列 q ,将入度为0的节点加入队列。

当队列非空时,移除队首元素 v ,将 cnt 加一 (更节省内存的做法是 numCourse 减一),然后将 v 的所有邻接点的入度减一,若v 的某邻接点 w 入度变为0,将其加入队列。

最后若numCourse 为0,则图无环,可完成拓扑排序。

posted @ 2020-03-29 22:08  bloglxc  阅读(399)  评论(0编辑  收藏  举报