刷题篇--热题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 }