刷题篇--热题HOT 01-10

1.两数之和(S)

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
分析:第一想法是按照剑指offer中的思路(双指针)解决,但是剑指Offer中题目是递增数组已经排好序了。

  因此需要换个思路。暴力解决,两遍遍历,但是复杂度高。可以使用map保存索引和值,减少一次遍历。那么map的key和value应该如何设置呢?如果key设置为下标,value设置为数组值,那么会造成再一次循环map寻找索引值。如果把key设置为数组值,value设置为索引,就不用再次遍历map了。

 1 class Solution {
 2     public int[] twoSum(int[] nums, int target) {
 3         int[] res = new int[2];
 4         Map<Integer,Integer> map = new HashMap();
 5         map.put(nums[0],0);
 6         for(int i=1;i<nums.length;i++){
 7             int dValue = target-nums[i];
 8             if(map.containsKey(dValue)&&map.get(dValue)!=i){//不重复利用同一元素
 9                 res[0]= map.get(dValue);
10                 res[1]= i;
11                 return res;
12             }else{
13                 map.put(nums[i],i);
14             }
15         }
16         return res;
17     }
18 }

 

2.两数相加(M)

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

 

 分析:原来想复杂了,两数相加大于等于十还用进位,后来发现不用,直接加上去就行了。(2+5)*1+(4+6)*10+(3+4)*100=7+100+700=807。

但是这个测试用例出错,原因是int溢出,所以不能简单地通过求出最后结果来执行。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int k = 1;//1 10 100 1000
        int res = 0;
        int temp;//当前两位和
        while(l1!=null&&l2!=null){
            temp = l1.val+l2.val;           
            res += temp*k; 
            k=k*10;
            l1 = l1.next;
            l2 = l2.next;
        }      
        //下边两个while只会执行一个(长度较长的那个链表)
        while(l1!=null){
            res += l1.val*k;
            k = k*10;
            l1 = l1.next; 
        }
        while(l2!=null){
            res += l2.val*k;
            k = k*10;
            l2 = l2.next; 
        }
        //构建链表
        int num = res%10;
        res=res/10;
        ListNode head = new ListNode(num);
        ListNode node = head;
        while(res!=0){
            num = res%10;
            node.next = new ListNode(num);
            node = node.next;
            res=res/10;    
        }
        return head;
    }
}

 

分析:吃一堑长一智,只能挨个节点计算了,不用新建链表,还是需要进位标志。。。(点个外卖先^_^)。标志位设置为01比true false好,自己在代码中体会(可参与计算,减少if else)。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int flag = 0;//进位标识符,0标识小于10不进位,1表示进位
        ListNode head = new ListNode(0);
        ListNode curNode = head;
        while(l1!=null||l2!=null){
            int x = (l1==null)?0:l1.val;
            int y = (l2==null)?0:l2.val;
            curNode.next = new ListNode((x+y+flag)%10);//如果上一位进位则flag=1,反之为0.
            curNode = curNode.next;
            flag = (x+y+flag)/10;//如果大于10,flag=1.肯定小于20,flag不是1就是0
            if(l1!=null) l1 = l1.next;
            if(l2!=null) l2 = l2.next;
        }
        if(flag==1) curNode.next = new ListNode(1);
        return head.next;
    }
}

 

3.【无重复字符的最长字串(M)】

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

分析:i和j分别为字串开头和结尾,本题只需求最长字串的长度,使用滑动窗口,记录最大值(每次滑动进行最大值比较)

  当i=0时:

    j=1时,判断字符串str[0,1)没有重复字符。

    j=2时,判断字符串str[0,2)没有重复字符。

    j=3时,判断字符串str[0,3)没有重复字符。

    j=4时,判断字符串str[0,4)没有重复字符。

  这种比较做了很多重复的工作,在比较当前字符时,把前面比较过的字串又比较了一遍。其实只需要判断当前字符是否在前面的子串中出现就行。

例如str[0,3)无重复字符,当j=4时,不用再判断str[0,4)中前三个,只需要判断j=4字符是否出现在str[0,3)中即可。

  如果出现了,比较最大不重复字串长度,并且使得i等于j当前字符上一次出现位置的下一个。例如adcd,当判断下一个字符c出现在abcd中时,如果让i+1,则c还是重复,简便操作就是从c下一个位置,即d开始遍历。

 

 

 

 

 

 1 class Solution {
 2     public int lengthOfLongestSubstring(String s) {
 3         //建立数组,索引值是字符ASCLL码,数组值是索引
 4         int[] charArr = new int[256];
 5         int maxSub = 0;//最长字串长度
 6         int i=0;//i是子字符串左边索引,j是右边索引的位置
 7         for(int j=0;j<s.length();j++){
 8             char ch = s.charAt(j);
 9             if(charArr[ch]>=i){//说明当前字符在之前子串中出现过
10                 i = charArr[ch];//字串左边移动
11             }
12             charArr[ch] = j+1;//存储(或改变)当前字符的位置
13             maxSub = Math.max(maxSub,j-i+1);
14         }
15         return maxSub;
16     }
17 }

 

 

4.【寻找两个有序数组的中位数(H)】
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
分析:中位数就是把序列“切割”,左半部分的值比中位数小,右半部分的值比中位数大。因此可以想到将两个数组进行分割,确保满足这样的定义。

其中会产生两数组长度之和为奇数偶数的区分讨论。

  数组划分(A数组划分为0~i-1,i~m-1;B数组划分为0~j-1,j~n-1)要求如下:

  1.若A数组和B数组总长度(m+n)是偶数:

    ①左半部分和右半部分数据数量相同:i-1+j-1 = m-1-i+n-1-j  =>  i+j=(m+n)/2  => j=(m+n)/2-i

    ②保证A左半部分最大值(最右)小于等于B右半部分最小值(最左),且B左半部分最大值小于等于A右半部分最小值,即

      A[i-1]<=B[j] && B[j-1]<=A[i]

  2.若A数组和B数组总长度(m+n)是奇数:

    ①左半部分比右半部分多一个元素:i-1+j-1 = m-1-i+n-1-j+1  =>  i+j=(m+n+1)/2  => j=(m+n+1)/2-i

    ②保证A左半部分最大值(最右)小于等于B右半部分最小值(最左),且B左半部分最大值小于等于A右半部分最小值,即

      A[i-1]<=B[j] && B[j-1]<=A[i]

  ·当(m+n)为偶数时,(m+n)/2 = (m+n+1)/2,因此(m+n)两种情况可以合并成一种情况:

    j=(m+n+1)/2-i    ——1

    A[i-1]<=B[j]   ——2

    B[j-1]<=A[i]   ——3

    [注意]要保证m<n,否则j会为负数

  只要满足上述三个情况,就可以求出中位数的值:如果(m+n)是偶数,那么中位数的值(max(A[i-1],B[j-1])+min(A[i],B[j]))/2,如果(m+n)是奇数,中位数的值是max(A[i-1],B[j-1])。

  根据第一个条件,直到i的值便确定了j的值,因此只需要改变i的值寻找满足2、3条件的位置就可以确定左半部分和右半部分。复杂度要求为log(m+n),根据log可以想象到递归、二分。

 

  在划分过程中,若A[i-1]>b[j],则i向左移动,若B[j-1]>A[i],则i向右移动。

 【示例】

m=7,n=8,(m+n)=15 奇数 ; j=16/2-i=8-i;  左半部分

①i=(0+6)/2=3,j=8-3=5;

 A[i-1]=13,A[i]=14;B[j-1]=11,B[j]=12  =>   A[i-1]>B[j]  =>i=(0+3)/2=1

②i=1,j=7;

  A[i-1]=2,A[i]=4;B[j-1]=12,B[j]=15  =>  B[j-1]>A[i]  =>i=(1+3)/2=2

③i=2,j=6

  A[i-1]=4,A[i]=13;B[j-1]=12,B[j]=12  => A[i-1]<=B[j] && B[j-1]<=A[i] √

中位数等于max(A[i-1],;B[j-1])=12

 1 import java.lang.Math;
 2 class Solution {
 3     public double findMedianSortedArrays(int[] nums1, int[] nums2) {
 4         int m = nums1.length;
 5         int n = nums2.length;
 6         if(m>n) return findMedianSortedArrays(nums2,nums1);
 7         int left = 0;//left.right用于计算i
 8         int right = m;
 9         int i=0, j=0;
10         while(left<=right){
11             i = (left+right)/2;
12             j = (m+n+1)/2-i;
13             if(i>left&&nums1[i-1]>nums2[j]){
14                 right = i-1;
15             }else if(i<right&&nums2[j-1]>nums1[i]){
16                 left = i+1;
17             }else{
18                 int maxLeft=0,minRight=0;
19                 //确定左半部分最大值,注意边界情况
20                 if(i==0){
21                     maxLeft = nums2[j-1];
22                 }else if(j==0){
23                     maxLeft = nums1[i-1];
24                 }else{
25                     maxLeft = Math.max(nums1[i-1],nums2[j-1]);
26                 }
27                 if((m+n)%2==1){
28                     return maxLeft;
29                 }
30                 //确定右半部分最小值
31                 if(i==m){
32                     minRight = nums2[j];
33                 }else if(j==n){
34                     minRight = nums1[i];
35                 }else{
36                     minRight = Math.min(nums1[i],nums2[j]);
37                 }
38                 return (maxLeft+minRight)/2.0;
39                 
40             }        
41         }   
42         return 0.0; 
43     }
44 }

 

 

5.最长回文字串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

输入: "babad"输出: "bab"注意: "aba" 也是一个有效答案。输入: "cbbd" 输出: "bb"

法一:回文串一定是对称的,例如 aba、abba,因此在遍历字符串时,可以查看以当前字符为中心(或者和下一个字符两个一起)的长度,记录开始和结束坐标,每次遍历比较回文串长度进行更新。时间复杂度O(N^2) 空间复杂度 O(1)

 1 import java.lang.Math;
 2 class Solution {
 3     public String longestPalindrome(String s) {
 4         if(s==null||s.length()==0) return "";
 5         int start = 0, end = 0;//记录每一次遍历最大的回文串开始和结束位置
 6         for(int i=0;i<s.length();i++){
 7             int len1 = expandAroundCenter(s, i, i);
 8             int len2 = expandAroundCenter(s, i, i+1);
 9             int len = Math.max(len1,len2);
10             if(len>(end-start)){
11                 start = i-(len-1)/2;
12                 end = i+len/2;
13             }
14         }
15         return s.substring(start,end+1);
16         
17     }
    //返回回文串长度
18 public int expandAroundCenter(String s, int left, int right){ 19 while(left>=0&&right<s.length()&&s.charAt(left)==s.charAt(right)){ 20 left--;right++; 21 } 22 return right-left-1; 23 } 24 }

 

 

6.Z字形变换

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:
 
L       C       I      R
E  T  O  E  S  I   I   G   输出  LCIRETOESIIGEDHN
E       D      H     N

输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
L         D       R
E    O  E     I  I
E  C     I   H   N
T         S       G

分析:可以预设列表存储每行的数据(列表中每一个元素就是一个StringBuilder),遍历字符串,往列表中添加元素,设置Boolean标志前进方向(列表索引).

 1 class Solution {
 2     public String convert(String s, int numRows) {
 3         if(numRows==1||numRows>=s.length()) return s;
 4         List<StringBuilder> list = new ArrayList();
 5         for(int i=0;i<numRows;i++){
 6             list.add(new StringBuilder());//list中每一个元素存储每行字符串
 7         }
 8         int rows = 0;//列表索引 0~numRows-1,rows=0时,往下走;rows=numRows-1时,往上走
 9         boolean turnRound = false;//是否掉头存储 rows=0时,为false,往下走,rows=numRows-1时,为true,往上走
10         for(char ch:s.toCharArray()){
11             list.get(rows).append(ch);
12             if(rows==0||rows==numRows-1) turnRound = !turnRound;
13             rows += turnRound ? 1 : -1;
14         }
15         String res = "";
16         for(StringBuilder sb : list){
17             res += sb;
18         }
19         return res;
20     }
21 }

 

 

10.正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素

 输入: s = "aa" p = "a" 输出: false;输入: s = "aa" p = "a*" 输出: true;输入: s = "ab" p = ".*" 输出: true;输入: s = "aab" p = "c*a*b" 输出: true;

输入: s = "mississippi" p = "mis*is*p*." 输出: false;

详解请点击:题二

 1 class Solution {
 2     public boolean isMatch(String s, String p) {
 3         if(s==null||p==null) return false;
 4         char[] str = s.toCharArray();
 5         char[] pattern = p.toCharArray();
 6         return match(str, 0, pattern, 0);
 7     }
 8     public boolean match(char[] str, int i, char[] pattern, int j){
 9         //结束条件
10         if(j==pattern.length){
11             return i==str.length;
12         }
13         //若当前字符存在下一个字符,判断下一个字符是‘*’,若是
14         if(j<pattern.length-1&&pattern[j+1]=='*'){
15             //当前字符匹配
16             if(i!=str.length&&(str[i]==pattern[j]||pattern[j]=='.')){
17                 //aab   a*b;  abc a.*bc  ;abc a*bc;
18                 return match(str,i+1,pattern,j)||match(str,i,pattern,j+2);
19             }else{//当前字符不匹配 
20                 //跳过pattern当前字符和后一个‘*’字符 abc v*abc
21                 return match(str,i,pattern,j+2);
22             }
23         }
24         //若当前字符存在下一个字符,判断下一个字符是‘*’,若不是
25         if(i!=str.length&&(pattern[j]=='.'||str[i]==pattern[j])){
26             return match(str,i+1,pattern,j+1);
27         }
28         return false;
29     }
30 }

 

 

11.盛水最多的容器

给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。输入: [1,8,6,2,5,4,8,3,7],输出: 49

说明:你不能倾斜容器,且 n 的值至少为 2。

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

 

分析:初设最大盛水量max=0,任意两垂线{(i,0),(i,ai); (j,0),(j,aj)} (j>i)之间的盛水量为(j-i)*min{ai,aj}。盛水量主要取决于较短的高度和之间距离,当一条垂线小于另外一条垂线时,就要变化。如果当前高度比另外一方高,则无需移动,如果移动,不仅高度会变短,而且之间距离也会变近,这是没有必要的,只有当比另外一方小了,当前垂直线变成了“短板”角色,才要寻找有没有比自己还要高的。综上:只有自己成了“短板”,才会移动。

 1 class Solution {
 2     public int maxArea(int[] height) {
 3         int maxCap = 0;
 4         int left = 0, right = height.length-1;
 5         while(left<right){
 6             int curCap = (right-left)*Math.min(height[left],height[right]);
 7             maxCap = maxCap>curCap?maxCap:curCap;
 8             if(height[left]<height[right]){
 9                 left++;
10             }else{
11                 right--;
12             }
13         }
14         return maxCap;
15     }
16 }

 

 

15.三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

分析:暴力解决就是三重循环,复杂度O(N^3),既然复杂度这么高,不如先将数组进行排序,然后进行两次循环。

nums = [-4, -1, -1, 0, 1, 2],通过遍历数组i,并且设置双指针:left=i+1,right=length-1,(初始)

① i=0,  left =1, right = 5, nums[i] = -4, nums[left]=-1, nums[right]=2

  nums[i]+nums[left]+nums[right]=-3<0   =>说明 nums[left]太小了,需要增大

 i=0时left不断增大,都不满足条件。

② i=1,  left =2, right = 5, nums[i] = -1, nums[left]=-1, nums[right]=2

  nums[i]+nums[left]+nums[right]=0,满足条件,加入结果列表。继续遍历,注意去重(nums[left]=nums[left+1],nums[right]=nums[right+1]),要不然会有两个相同的结果(虽然索引不一样,但是值相同)

……

 1 class Solution {
 2     public List<List<Integer>> threeSum(int[] nums) {
 3         List<List<Integer>> res = new ArrayList();
 4         if(nums==null||nums.length<3) return res;
 5         Arrays.sort(nums);
 6         int left=0,right=nums.length-1;
 7         for(int i=0;i<nums.length-2;i++){
 8             if(nums[i]>0) break;
 9             if(i>0 && nums[i]==nums[i-1]) continue;//去重
10             left = i+1;
11             right = nums.length-1;
12             while(left<right){
13                 int sum = nums[i] + nums[left] + nums[right];
14                 if(sum<0) left++;
15                 if(sum>0) right--;
16                 if(sum==0){
17                     List<Integer> list = new ArrayList();
18                     list.add(nums[i]);
19                     list.add(nums[left]);
20                     list.add(nums[right]);
21                     while(left<right && nums[left]==nums[left+1]) left++;//去重
22                     while(left<right && nums[right]==nums[right-1]) right--;
23                     res.add(list);
24                     left++;
25                     right--;
26                 }
27             }
28             
29         }
30         return res;
31     }
32 }

 

 

17.电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

 

 

 输入:"23",输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

分析:使用递归,先遍历输入数字字符代表的第一个字符串数组,每次遍历中,递归下一个数字字符代表的字符串数组。

 1 class Solution {
 2     List<String> list = new ArrayList();
 3     String[] mods = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
 4     public List<String> letterCombinations(String digits) {
 5         if(digits==null||digits.length()==0) return list;
 6         findCombinations(digits, 0, "");
 7         return list;
 8     }
 9     public void findCombinations(String digits, int index, String s){
10         //结束条件
11         if(digits.length()==s.length()){//输入数字字符串长度和排列结果长度相同
12             list.add(s);
13             return;
14         }
15         char ch = digits.charAt(index);//得出输入数字当前数字字符,先是2,再是3  //index表示当前遍历到digits第几个字符
16         String mod = mods[ch-'0'];//得出当前数字字符对应的字符串  2对应abc  3对应def  //ch-'0'表示数组索引
17         for(int i=0;i<mod.length();i++){//先遍历abc,abc又对应着def,即a->def b->def c->def
18             findCombinations(digits, index+1, s+mod.charAt(i));
19             //findCombinations(digits, 1, s+"a")
20                 //findCombinations(digits, 2, s+"ad")
21                 //findCombinations(digits, 2, s+"ae")
22                 //findCombinations(digits, 2, s+"af")
23             //findCombinations(digits, 1, s+"b")
24             //.
25             //.
26             //.
27             //findCombinations(digits, 1, s+"c")
28             //.
29             //.
30             //.
31         }
32     }
33 }

 

 

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

给定一个链表: 1->2->3->4->5, 和 n = 2.当删除了倒数第二个节点后,链表变为 1->2->3->5.

分析:快慢指针,快指针先走n+1步。然后快慢指针一起跑,然后慢指针指到待删除结点的上一个节点,此时再进行删除操作
 1 /**
 2  * Definition for singly-linked list.
 3  * public class ListNode {
 4  *     int val;
 5  *     ListNode next;
 6  *     ListNode(int x) { val = x; }
 7  * }
 8  */
 9 class Solution {
10     public ListNode removeNthFromEnd(ListNode head, int n) {
11         ListNode pre = new ListNode(0);//新增一个头节点,防止倒数第n个是头节点
12         pre.next = head;
13         ListNode fastNode = pre;
14         ListNode slowNode = pre;
15         for(int i=0;i<n+1;i++){
16             fastNode = fastNode.next;
17         }
18         while(fastNode!=null){
19             fastNode = fastNode.next;
20             slowNode = slowNode.next;
21         }
22         slowNode.next = slowNode.next.next;
23         return pre.next;
24 
25     }
26 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 1 class Solution {
 2     public boolean isMatch(String s, String p) {
 3         if(s==null||p==null) return false;
 4         char[] str = s.toCharArray();
 5         char[] pattern = p.toCharArray();
 6         return match(str, 0, pattern, 0);
 7     }
 8     public boolean match(char[] str, int i, char[] pattern, int j){
 9         //结束条件
10         if(j==pattern.length){
11             return i==str.length;
12         }
13         //若当前字符存在下一个字符,判断下一个字符是‘*’,若是
14         if(j<pattern.length-1&&pattern[j+1]=='*'){
15             //当前字符匹配
16             if(i!=str.length&&(str[i]==pattern[j]||pattern[j]=='.')){
17                 //aab   a*b;  abc a.*bc  ;abc a*bc;
18                 return match(str,i+1,pattern,j)||match(str,i,pattern,j+2);
19             }else{//当前字符不匹配 
20                 //跳过pattern当前字符和后一个‘*’字符 abc v*abc
21                 return match(str,i,pattern,j+2);
22             }
23         }
24         //若当前字符存在下一个字符,判断下一个字符是‘*’,若不是
25         if(i!=str.length&&(pattern[j]=='.'||str[i]==pattern[j])){
26             return match(str,i+1,pattern,j+1);
27         }
28         return false;
29     }
30 }
posted @ 2019-12-28 14:04  Qmillet  阅读(331)  评论(0编辑  收藏  举报