leetcode思路简述(1-30)

1. 两数之和

 把遍历过的数放在字典,数字做key,序号做value。遍历时先查字典,target减它是否为字典中key。时间复杂度O(n)。

 

2. 两数相加

可把比较短的链表差的位数置零补充。

 

3. 无重复字符的最长子串  

可以字典存储滑动窗口,[i, j) 遇到重复i+1否则j+1。

 

4. 寻找两个有序数组的中位数

关键是只要确定一个数组切点的位置,另一个也就确定了。通过二分判断切点左移还是右移。

中位数的左右两边数量相同,可知中位数分隔线左边数字的数量k=(len1+len2+1) // 2。

在nums1 中取m1个数,nums2 中取m2个数,一起作为中位分隔线的左边数字,m1 + m2 = k。

找到合适的m1与m2使用二分法。使用较短的数组进行二分,设置初始left和right为较短的数组的左右边界。每次使m1 = left +(right - left)//2,m2 = k - m1。

在二分过程中,如果 nums1[m1] < nums2[m2-1],则left = m1 + 1;否则 right = m1。直到left < right。这个过程与常规的二分法完全一致,只是左右区间进行选择时的条件不同。

二分循环结束,得到m1与m2。判断总长度是奇数还是偶数确定返回值。

 

5. 最长回文子串

① 中心扩散

② Manacher

 

6. Z字形变换

不要弄太复杂,遍历字符竖着返回到numRows个字符串中,触底反弹。最后把三行直接连一起。

 

7. 整数反转

取最后一位用%,去掉最后一位用//。

 

8. 字符串转换整数 (atoi)

可正则可if各种方法注意细节。

 

9. 回文数

直接翻转对比是否与原数相等。

考虑到溢出,可以只翻转一半。如何判断翻转到一半:当原始数字小于等于反转后的数字时,表示已经处理了一半的数字,对比两半是否相等(总长为偶数),或反转的一半//10后与另一半是否相等(总长为奇数)。

 

10. 正则表达式匹配  *

① 回溯:pattern第二位是*,则 (1)  isMatch(text, pattern[2:])        (*前面字符匹配数为0)

               或(2)first_match and self.isMatch(text[1:], pattern))     (第一位匹配,text看下一位,pattern不变)

    第二位非*,则 first_match and self.isMatch(text[1:], pattern[1:])     (第一位匹配,两个都看下一位)

② 动态规划

  将中间结果保存,dp[i][j] 表示 text 的前 i 个是否能被 pattern 的前 j 个匹配

 

11. 盛最多水的容器

双指针。由于水容量取决于较短的边,所以即使将长的边向内移动也不会增加水量,因此移动短边。

 

12. 整数转罗马数字

把特殊的9啊4啊什么的都加到罗马字符中,然后每次取可取的最大数。

 

13. 罗马数字转整数

除了每次看i和i+1的方法之外,也可以:若上一个字符比当前小,则减去二倍较小字符。以上两题可用字典。

 

14. 最长公共前缀

在最简单的做法基础上,还有二分法,字典树等进阶方法。

 

15. 三数之和

先排序。令i为当位置,找到nums[i]与另外两数之和为0(如果i与i-1的值相同则跳过以去掉重复组合),设置左右指针L=i+1,R=n-1。

若nums[i]+L+R等于0则保存这组并LR同时向中间移动(跳过相同值)。若和大于0:R左移;若和小于0:L右移。

 

16. 最接近的三数之和

与上题相似,就是大于小于0变为target。

 

17. 电话号码的字母组合

 开始写的每轮循环ans要append数字对应的每一字母,pop它前面短的单词。回溯传值为整个ans,然后下一个再加一个字母这样。

其实可以对数字对应的每一字母回溯,只传单个的单词,到了数字长度为0时最后才append。

 

18. 四数之和

在三数之和上改改,左右边界为i,j 。i从0开始往后,内层循环j从len-1往前,且 i - j >= 2。中间双指针。这里去重不能遇到重复则多移动一位,这样仍然会重复(不过可以减少循环次数),可以在append前用not in判断下,因为有序的如果重复的话数字顺序一定相同。

另外有一些优化:(i, L, R, j)

(1) 如果 nums[i] + 3 * nums[i + 1] > target 则break;

(2) 如果 nums[i] + 3 * nums[-1] < target,那么当前的 nums[i] 加其余三个数一定小于 target,i直接continue看下一个;

(3) 同理,j可同样优化。

最后也看到有做法把前两层循环ij的顺序设置为(i, j, L, R),可以直接通过遇重多移一次避免重复的问题,其他应该差不多一个意思。

 

19. 删除链表的倒数第N个节点

先一个指针p遍历,经过第n个节点时,另一指针pre从head开始一起遍历,p.next为空时,pre.next=pre.next.next。

注意细节,若count<n,就是删除第一个节点,直接返回head.next即可。

 

20. 有效的括号

栈。用字典左括号右括号匹配可以少写点if。最后记得判断栈是否为空。

 

21. 合并两个有序链表

。。。

 

22. 括号生成

左括号数left,右括号数right,回溯若left+right == n则append,若left > right: 加一个右括号;若left < N: 加个左括号

 

23. 合并K个排序链表

分治(Nlogk),N为所有链表节点数之和。取两个pop(0)链表,合并,append,直到len(lists) == 1

 

24. 两两交换链表中的节点

递归;迭代。

代码美观真的很重要呀。

 

25. K 个一组翻转链表  *

① 用栈。每次k个进栈,然后出栈 p.next = stack.pop()。剩下不到k个则直接连。

② 尾插法。依次把cur移到tail后面

③ 递归+尾插。把cur移动到第k个,然后 cur = self.reverseKGroup(cur, k),再翻转前k个

 

26. 删除排序数组中的重复项

① 双指针。i遍历所有,若i和j对应的值不同,nums[j]=nums[i],j+1。就是把不相同的值移到前面,最后只返回前j个。

② 把上个值保存到pre里,i遍历,若相同则pop(i),不同则pre = nums[i]。

 

27. 移除元素

和26差不多。快慢指针可。还可以将当前与末尾元素交换,减少复制次数,相同则nums[i] = nums[n],n--,不同则i++。

 

28. 实现 strStr()

① 暴力。避免不必要的遍历,

② KMP。

1. 根据子串字符串构造一个next部分匹配表。

2. 遍历主串字符串,当匹配失效时,查询next部分匹配表定位子串接着与主串比较的位置。

使用双指针遍历,i指针遍历子串,如果没有相等元素,j指针保留在头部,如果遇到相同元素,j指针后移,当元素再次不相同时,j指针回到头部。可以看到,其实i指针后缀,j指针前缀,实现前后缀相同元素的计数。

构造next,从字符串的第一位(不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。

构造好子串的next表后,i指针遍历主串,当遇到子串首元素时,i,j同时前进,当匹配失效时,查找next表中当前元素的值,将j指针移动到该处,回溯就是 i = next[i]。

 

29. 两数相除  *

每次除数加倍,商加倍。当被除数小于除数的时候,商与之前的累加,除数和商还原为初始值,然后继续倍增。

 

30. 串联所有单词的子串

首先肯定是要用一个len(words) * words[0]大小的窗口遍历。然后是对截取的每个窗口进行判断:

 ① 先把所有单词存到 dict1 = Counter(words),扫描窗口,如果当前单词在dict1 中,就把该单词存到新的 dict2中。若dict2中value大于dict1的,break,看下一个窗口;若小于等于,看窗口的下个单词。子串所有单词判断完表示符合,append。

② 改进版。使窗口每次移动三位(前三位都是窗口起点,分别遍历)。有三个改进点(可用双指针实现,left指向子串开头,right指向子串当前检查的单词右边):

    (1)出现不匹配单词,下一个子串从不匹配的单词之后开始。

    (2)出现匹配的单词,但是次数超了,比如foo多了一次,则从foo在子串第一次出现的位置之后开始下一个子串,把前面的单词在字典中去掉。实现起来的话,while(dict2[w] > dict1[w]),移动left即可,left移动时顺便修改字典。

    (3)当上一个子串匹配成功时,下一个dict2不需清空重来,只需要把之前第一个单词去掉,再检查新的最后一个单词。

* 用字典可以避免纠结子串的单词顺序;对于算法优化,去分析哪些判断是不必要的,改进。

 

posted @ 2020-03-05 01:31  肃木易  阅读(200)  评论(0编辑  收藏  举报