XX-YY-ZZ-W
XX用来初步定义难度的范围(用于决定应投入多少时间在复习该题上):
按照难度进行排序,难度划分为:易、中和难。
易题:基本不用复刷,完全掌握。
中题:需要复刷1-2遍后可完全掌握。(中等-低,中等-高 建议隔1周复刷1次,总计1次;中等-高建议隔1周复刷1次,总计2次)
难题:需要至少刷2遍以上才能最终掌握(难-低 建议隔1周复刷1次,总计3次;难-中 建议隔1周复刷1次,总计4次;难-高 建议隔1周复刷1次,总计5次)。
YY代表细分定义后的难度(用于决定该复习一道题的次数和优先级):
细分定义的难度划分为:低、中和高。
ZZ代表将该题列入该难度范围的原因:
包括:细节易错,思路难想,复杂度高,思路不清晰,原理不理解等。
W代表最近重做时能否独立做出:
1代表是在借鉴答案后完成,记忆不深刻,要在下次优先重做巩固。
目录
1、K个一组翻转链表(难-高-细节易错)
难点1:本题中难点1在于如何将部分链表反转。思路是将某段链表从头到尾的指针反向。最终head和tail的相对位置不变,但可以从tail往前遍历,于是乎把tail赋值给newHead,head赋值给newTail,于是可以从newHead来开始遍历整段链表。
难点2:本题中难点2在于要维护几个指针,如何不停地将指针摆放到合适位置。思路是只维护4个指针,分别是pre、head、tail、nex。head和tail指向要反转链表的头和尾,这两个指针是重点维护对象。
重点1:rightNode这条语句一定要在判断rightNode是否为空的语句前面。
重点2:如果最后几个元素个数不足k直接返回,不用作任何改变
重点3和4:重新改变head和tail的下标
重点5:prev要指向tail的下一位
易错点1:tail指向pre
代码:
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummyNode = new ListNode(-1,head);
ListNode pre = dummyNode;
while(head!=null){
ListNode tail = pre; //易错点1:tail指向pre
for(int i=0;i<k;i++){
tail = tail.next; //重点1:这条语句一定要在if语句前面
if(tail==null){
return dummyNode.next; //重点2:如果最后几个元素,直接返回,不用改
}
}
ListNode nex = tail.next;
ListNode[] list = reverse(pre.next,tail);
head = list[0]; //重点3:重新改变leftNode的下标
tail = list[1]; //重点4:重新改变rightNode的指向
pre.next = head;
tail.next = nex;
pre = tail;
head = nex;
}
return dummyNode.next;
}
//难点1:这个函数的作用是将某段链表从头到尾的指针反向。最终head和tail的相对位置不变,但可以从tail往前遍历,整段链表。
public ListNode[] reverse(ListNode head,ListNode tail){
ListNode prev = tail.next; //重点5: 是prev = tail.next
ListNode cur = head;
while(prev!=tail){ //重点6:是prev != tail
ListNode nex = cur.next;
cur.next = prev;
prev = cur;
cur = nex;
}
return new ListNode[]{tail,head};
}
}
2、下一个排列(难-高-细节易错)
易错点1:i>=0漏了,nums[i]是>=nums[i+1],等于不可漏掉
易错点2:nums[i]>=nums[j],等于不可漏掉
易错点3:reverse不包含在if语句内
易错点4:是i+1不是i,是n-1不是n
代码:
class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
int i = n-2;
while(i>=0 && nums[i]>=nums[i+1]){ //易错点1:i>=0漏了,nums[i]是>=nums[i+1],等于不可漏掉
i--;
}
if(i>=0){
int j = n-1;
while(j>=0 && nums[i]>=nums[j]){ //易错点2: nums[i]>=nums[j],等于不可漏掉
j--;
}
swap(nums,i,j);
}
reverse(nums,i+1,n-1); //易错点3:reverse不包含在if语句内
//易错点4:是i+1不是i,是n-1不是n
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums,int i,int j){
while(i<j){
swap(nums,i,j);
i++;j--;
}
}
}
3、合并K个排序链表(难-高-细节易错)
易错点1:继承Comparable里面还要写<Status>
易错点2:方法名叫作compareTo,参数是这个类的类型
易错点3:加入的元素是类类型,必须用Node(参数,参数)形式传递
易错点4:要取的是类的ListNode的元素
易错点5:取的是当前节点的下一个节点不为空
易错点6:加入的元素是类类型,必须用Node(参数,参数)形式传递
代码:
class Solution {
//ptr
class Status implements Comparable<Status>{ //易错点1:继承Comparable里面还要写<Status>
int val;
ListNode ptr;
Node(int val,ListNode ptr){
this.val = val;
this.next = ptr;
}
public int compareTo(Status status){ //易错点2:方法名叫作compareTo,参数是这个类的类型
return this.val-status.val;
}
}
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<Node> pq = new PriorityQueue<>();
for(ListNode node:lists){
if(node!=null) pq.add(new Status(node.val,node)); //易错点3:加入的元素是类类型,必须用Node(参数,参数)形式传递
}
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while(!pq.isEmpty()){
Status p = pq.poll();
tail.next = p.ptr; //易错点4:要取的是类的ListNode的元素
tail = tail.next;
if(p.ptr.next!=null){ //易错点5:取的是当前节点的下一个节点不为空
pq.add(new Status(p.ptr.next.val,p.ptr.next)); //易错点6:加入的元素是类类型,必须用Node(参数,参数)形式传递
}
}
return dummy.next;
}
}
4、编辑距离(难-高-原理不理解)
易错点1:判断是否其中一个串为空时,返回的是另一个串长度
易错点2:i和j需要从0一直遍历到n和m,而不是小于n和m
重点1:假如第i-1个元素和第j-1个元素相同,只需要加上1个元素就相等,不需要做任何操作。如果第i-1个元素和低j-1个元素不相同,需要修改1次,所以+1
重点2:不需要设置min变量,而是直接返回dp[n1][n2]所存储的值。
class Solution {
public int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();
if(n1*n2==0) return n+m; //易错点1:其中一个串为空,返回另一个串长度
int[][] dp = new int[n+1][m+1];
for(int i=0;i<=n;i++){ //易错点2:i和j需要从0一直遍历到n和m,而不是小于n和m
dp[i][0] = i;
}
for(int j=0;j<=m;j++){
dp[0][j] = j;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
int left = dp[i-1][j]+1; //在B添加字符?
int down = dp[i][j-1]+1; //在A添加字符?
int left_down = dp[i-1][j-1];
//重点1:假如第i-1个元素和第j-1个元素相同,只需要加上1个元素就相等,不需要做任何操作
//如果第i-1个元素和低j-1个元素不相同,需要修改1次,所以+1
if(word1.charAt(i-1)!=word2.charAt(j-1)){
mid += 1;
}
dp[i][j] = Math.min(mid,Math.min(left,right));
}
return dp[n1][n2]; //重点2:不需要设置min变量,而是直接返回dp[n1][n2]所存储的值。
}
}
5、排序链表(难-中-思路不清晰)
重点1:在一个类里能定义多个相同函数名但参数不同的函数。
易错1:如果头节点下一个节点为尾节点要让头结点下一个节点置空,不然会返回尾结点。
易错2:求中点的时候,判断条件要是不等于尾结点,并且如果要访问fast.next.next结点,只需要fast.next不为null即可。
易错3:合并过程前期是比大小,最后是收尾阶段,收尾只收不为null的节点。
代码:
class Solution {
public ListNode sortList(ListNode head) {
return sortList(head,null); //重点1:能用相同函数名不同参数重写
}
public ListNode sortList(ListNode head,ListNode tail){
if(head==null) return head;
if(head.next==tail){
head.next =null; //易错1:head.next要置为null
return head;
}
ListNode slow = head,fast = head;
while(fast!=tail && fast.next!=tail){ //易错2:不是不等于null而是tail,fast!=tail&&fast.next!=tail
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow;
ListNode list1 = sortList(head,mid);
ListNode list2 = sortList(mid,tail);
ListNode sorted = merge(list1,list2);
return sorted;
}
public ListNode merge(ListNode list1,ListNode list2){
ListNode dummy = new ListNode(0);
ListNode temp = dummy,temp1 = list1,temp2 = list2;
while(temp1!=null && temp2!=null){
if(temp1.val<=temp2.val){
temp.next = temp1;
temp1 = temp1.next;
}else{
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if(temp1!=null){ //易错3:最后收尾,只需要收不为null的节点
temp.next = temp1;
}else if(temp2!=null){
temp.next = temp2;
}
return dummy.next;
}
}
6、数组中第K个最大元素(难-低-细节易错)
规范点:buildHeap最好改成buildMaxHeap。maxHeap最好改成maxHeapify。
易错点1:不是maxHeap(nums,i/2)而是maxHeap(nums,0,heapSize)因为从0开始表示从堆顶开始调整,还需要传入堆的大小,这个堆大小在改变中。
易错点2:这里不是nums[left]>nums[i]而是nums[left]>nums[maxPos],因为比较的是left下标和maxPos下标的元素值大小。
易错点3:这里要保证left和right<heapSize。
易错点4:maxHeap(nums,maxPos,heapSize)这个递归调用是放在if语句里面,只有当交换位置后才需要继续调整。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildHeap(nums,heapSize);
for(int i=nums.length-1;i>=nums.length-k+1;i--){
swap(nums,0,i);
heapSize--;
//易错1:不是maxHeap(nums,i/2)而是maxHeap(nums,0,heapSize)因为从0开始表示从堆顶开始调整,还需要传入堆的大小,这个堆大小在改变中。
maxHeap(nums,0,heapSize);
}
return nums[0];
}
public void buildHeap(int[] nums,int heapSize){
for(int i=heapSize/2;i>=0;i--){
maxHeap(nums,i,heapSize);
}
}
public void maxHeap(int[] nums,int i,int heapSize){
int left = i*2+1,right = i*2+2;
int maxPos = i;
//易错2:这里不是nums[left]>nums[i]而是nums[left]>nums[maxPos],因为比较的是left下标和maxPos下标的元素。
if(left<heapSize && nums[left]>nums[maxPos]){ //易错3:这里要保证left和right<heapSize
maxPos=left;
}
if(right<heapSize && nums[right]>nums[maxPos]){
maxPos=right;
}
if(maxPos!=i){
swap(nums,maxPos,i);
maxHeap(nums,maxPos,heapSize); //易错4:这个递归调用是放在if语句里面,只有当交换位置后才需要继续调整
}
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
7、三数之和(难-低-细节易错)
易错点1:数组不一定按照正序排列,所以要排序
易错点2:third记录的是末元素的下标,一开始写成third=nums[n-1]是错的
易错点3:second>first+1而不能是second>=first+1,不能取等,如果取等会和first重复
易错点4:向左移动third下标时使用的是while
易错点5:最后装入列表的要是3个元素,而不是3个元素的下标
class Solution {
public List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums); //易错点1:数组不一定按照正序排列,所以要排序
for(int first=0;first<n;first++){
if(first>=1&&nums[first]==nums[first-1]){
continue;
}
int target = -nums[first];
//nums[second]+nums[third]= -nums[first] ;
int third = n-1; //易错点2:一开始写成third=nums[n-1]是错的
for(int second=first+1;second<n;second++){
if(second>first+1&&nums[second]==nums[second-1]){ //易错点3:second>first+1而不能是second>=first+1
continue;
}
while(second<third&&nums[third]+nums[second]>target){ //易错点4:这里使用的是while
third--;
}
if(third<=second) break;
//nums[second]+nums[third]+nums[first]>0 ;
if(nums[third]+nums[second]==target){
List<Integer> l = new ArrayList<>();
l.add(nums[first]); //易错点5装入列表的要是3个元素,而不是3个元素的下标
l.add(nums[second]);
l.add(nums[third]);
ans.add(l);
}
}
}
return ans;
}
}
8、螺旋矩阵(难-低-思路不清晰-1)
规范:行用rows表示。列用columns表示。标志数组用visited来表示。具体数组下标行用row表示,列用column表示。数组下一个位置下标行用nextRow表示,列用nextColumn表示。
重点1:通过总数来控制流程 for(int i=0;i<total;i++)
重点2:在index = (index+1)%4这里更改了index,下面row和column的index都改变了,实现转向
重点3:这里是row+direction[index][1]而不是matrix[i][j]+direction[index][1],要得到的是下标。
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
//行用rows表示。columns用line表示。标志数组用visited来表示。具体数组下标行用row列用column即可。数组下一个位置下标行用nextRow列用nextColumn。
int rows = matrix.length;
int columns = matrix[0].length;
int[][] directions = new int[][]{{0,1},{1,0},{0,-1},{-1,0}};
int[][] visited = new int[rows+1][columns+1];
List<Integer> res = new ArrayList<>();
int row=0,column=0,index=0;
int total = rows*columns;
for(int i=0;i<total;i++){ //重点1:通过总数来控制流程
visited[row][column]=1;
res.add(matrix[row][column]);
//重点3:这里是i+direction[index][1]而不是matrix[i][j]+direction[index][1],得到的是下标
int nextRow = row+directions[index][0];
int nextColumn = column+directions[index][1];
if(nextRow<0 || nextRow>=rows || nextColumn<0 || nextColumn >= columns || visited[nextRow][nextColumn]==1){
index = (index+1)%4; //重点2:在这里更改了index,下面i和j的index都改变了,实现转向
}
row += directions[index][0];
column += directions[index][1];
}
return res;
}
}
9、合并区间(难-低)
难点1:对int[][]二维数组进行排序。
重点1:将Array转化为int[][],第1维要加大小。
代码:
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> list = new ArrayList<>();
Arrays.sort(intervals,new Comparator<int[]>(){ //难点1:对int[][]二维数组进行排序
public int compare(int[] o1,int[] o2){
return o1[0]==o2[0]?o1[1]-o2[1]:o1[0]-o2[0];
}
});
int n = intervals.length;
for(int i=0;i<n;i++){
int x = intervals[i][0];
int y = intervals[i][1];
int j=i+1;
while(j<n && y>=intervals[j][0]){
y = Math.max(y,intervals[j][1]);
i = j++; //重点1:对i进行更新的同时对j递增
}
list.add(new int[]{x,y});
}
return list.toArray(new int[list.size()][]); //重点2:将Array转化为int[][],第1维要加大小
}
}
10、复原IP地址(难-低)
易错点1:dfs(s,segId+1,segEnd+1); 传入的第2个参数要为segEnd+1。
重点1:注意规范,修改命名习惯:4改成SEG_COUNT;list改成ans;arr改成segments;seg改成segId;pos改成segStart;num改成addr。
代码:
class Solution {
//SEG_COUNT。list改成ans。arr改成segments。seg改成segId。pos改成segStart。num改成addr
public List<String> ans = new ArrayList<>();
int SEG_COUNT = 4;
public int[] segments = new int[4];
public List<String> restoreIpAddresses(String s) {
dfs(s,0,0);
return ans;
}
public void dfs(String s,int segId,int segStart){
if(segStart==s.length()){
if(segId==SEG_COUNT){
StringBuffer buf = new StringBuffer();
for(int i=0;i<SEG_COUNT;i++){
buf.append(segments[i]);
if(i!=SEG_COUNT-1) buf.append('.');
}
ans.add(buf.toString());
}
return;
}
if(segId==SEG_COUNT){
return;
}
if(s.charAt(segStart)=='0'){
segments[segId] = 0; //易错1:把segId和segStart要用segId
dfs(s,segId+1,segStart+1);
return;
}
int addr = 0;
for(int segEnd=segStart;segEnd<s.length();segEnd++){
addr = addr*10 + s.charAt(segEnd)-'0';
if(addr>=0 && addr<=255){
segments[segId] = addr;
dfs(s,segId+1,segEnd+1); //易错点2:传入的第2个参数要为segEnd+1
}else{
break;
}
}
}
}
11、滑动窗口最大值(难-低)
重点1:构造一个优先队列,元素为数组
重点2:熟练掌握添加(add)数组元素,取队首元素(peek),剔除队首元素(poll)。
易错点1:要剔除元素的左边界范围是小于等于i-k
易错点2:结果数组大小为n-k+1,结果数组赋值i-k+1
代码:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//重点1:构造一个优先队列,元素为数组
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] o1,int[] o2){
return o2[0]==o1[0]?o2[1]-o1[1]:o2[0]-o1[0];
}
});
for(int i=0;i<k;i++){
pq.add(new int[]{nums[i],i}); //重点2:熟练掌握添加(add)数组元素,取队首元素(peek),剔除队首元素(poll)。
}
int[] ans = new int[nums.length-k+1]; //易错点2:结果数组大小为n-k+1,结果数组赋值i-k+1
ans[0] = pq.peek()[0];
for(int i=k;i<nums.length;i++){
pq.add(new int[]{nums[i],i});
while(pq.peek()[1]<=i-k){ //易错点1:要剔除元素的左边界范围是小于等于i-k
pq.poll();
}
ans[i-k+1] = pq.peek()[0];
}
return ans;
}
}
12、缺失的第一个正数(难-低)
重点1:因为数组下标要>=0和<n,所以nums[i]>0,nums[i]<=n。
重点2:本题的思路是将从1开始的数放入数组,而数组以0开头,所以必须是nums[i]-1来和i比。
重点3:比如当i=0是nums[i]为4,要比较nums[3]的值是否和nums[0]相同,如果相同代表4已经放在了合适的位置就不必再交换了,很妙的思路。
代码:
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for(int i=0;i<n;i++){
//重点1:因为数组下标要>=0和<n,所以nums[i]>0,nums[i]<=n
//重点2:本题的思路是将从1开始的数放入数组,而数组以0开头,所以必须是nums[i]-1来和i比
//重点3:比如当i=0是nums[i]为4,要比较nums[3]的值是否和nums[0]相同,如果相同代表4已经放在了合适的位置就不必再交换了,很妙的思路
while(nums[i]>0 && nums[i]<=n && nums[nums[i]-1]!=nums[i]){
swap(nums,nums[i]-1,i);
}
}
for(int i=0;i<n;i++){
if(nums[i]!=i+1) return i+1;
}
return n+1;
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
13、最长公共子序列(难-低-原理不理解)
代码:
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int n1 = text1.length(),n2 = text2.length();
int[][] dp = new int[n1+1][n2+1];
//当i为0或j为0代表text为空,此时公共子序列长度为0。
//dp[i-1][j-1]存储的是text1[0到i-1],text2[0到j-1]的公共子序列长度。
for(int i=1;i<=n1;i++){
int x = text1.charAt(i-1);
for(int j=1;j<=n2;j++){
int y = text2.charAt(j-1);
if(x==y){ //代表text1[0:i-1]和text2[0:j-1]含有公共子序列。
dp[i][j] = dp[i-1][j-1]+1; //加上“1”个公共元素。
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n1][n2];
}
}
14、锯齿形层序遍历(难-低)
import java.util.*;
class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(){};
public TreeNode(int val){
this.val = val;
}
}
public class Receive {
public static List<List<Integer>> list;
public static Queue<TreeNode> queue ;
public static List<List<Integer>> zigzagLevelOrder(TreeNode root){
if(root==null) return list;
queue = new ArrayDeque<>();
list = new ArrayList<>();
queue.offer(root);
Boolean isLeft = true;
while(!queue.isEmpty()){
int n = queue.size();
Deque<Integer> deque = new ArrayDeque<>();
for(int i=0;i<n;i++){
TreeNode node = queue.poll();
if(isLeft){
deque.addLast(node.val);
}else{
deque.addFirst(node.val);
}
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
isLeft = !isLeft;
list.add(new ArrayList<>(deque));
}
return list;
}
public static void printTree(){
for(int i=0;i<list.size();i++){
System.out.print(list.get(i));
if(i!=list.size()) System.out.print(",");
}
}
public static void main(String[] args){
TreeNode[] p = new TreeNode[8];
p[0] = new TreeNode(3);
p[1] = new TreeNode(9);
p[2] = new TreeNode(20);
p[3] = new TreeNode(15);
p[4] = new TreeNode(7);
p[0].left = p[1];
p[0].right = p[2];
p[2].left = p[3];
p[2].right = p[4];
zigzagLevelOrder(p[0]);
printTree();
}
}
15、二叉树最近公共祖先(难-低)
class Solution {
Map<TreeNode,TreeNode> parent = new HashMap<>();
Set<TreeNode> set = new HashSet<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
dfs(root);
while(p!=null){
set.add(p);
p = parent.get(p);
}
while(q!=null){
if(set.contains(q)){
return q;
}
q = parent.get(q);
}
return null;
}
public void dfs(TreeNode head){
if(head==null) return;
if(head.left != null){
parent.put(head.left,head);
dfs(head.left);
}
if(head.right != null){
parent.put(head.right,head);
dfs(head.right);
}
}
}
16、重排链表(难-低)
class Solution {
public void reorderList(ListNode head) {
if(head==null){
return ;
}
ListNode mid = middle(head);
ListNode l1 = head;
ListNode l2 = mid.next;
mid.next = null;
l2 = reverse(l2);
merge(l1,l2);
}
public void merge(ListNode l1,ListNode l2){
ListNode tmp1;
ListNode tmp2;
while(l1!=null && l2!=null){
tmp1 = l1.next;
tmp2 = l2.next;
l1.next = l2;
l1 = tmp1;
l2.next = l1;
l2 = tmp2;
}
}
public ListNode reverse(ListNode head){
ListNode prev = null;
ListNode cur = head;
while(cur!=null){
ListNode nex = cur.next;
cur.next = prev;
prev = cur;
cur = nex;
}
return prev;
}
public ListNode middle(ListNode head){
ListNode tail = head;
ListNode mid = head;
while(tail != null && tail.next != null){
tail = tail.next.next;
mid = mid.next;
}
return mid;
}
}
17、二叉树中的最大路径和(难-低)
class Solution {
Integer res = -Integer.MAX_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return res;
}
public int dfs(TreeNode root){
if(root==null) return 0;
int left = Math.max(dfs(root.left),0); //有些节点值为负数
int right = Math.max(dfs(root.right),0);
int val = root.val + left + right;
res = Math.max(res,val);
return Math.max(left + root.val , right + root.val); //返回值为左右路径较大的一支
}
}
18、在排序数组中查找元素(中等-高)
注意点1:直接把first和end设置为-1如果没有nums[mid]==target可以直接返回-1更加方便。
重点1:想获得最右侧边界的值,让left指针为mid+1,直接在右半部分接着搜索。
代码:
class Solution {
public int[] searchRange(int[] nums, int target) {
int n = nums.length;
int left=0,right=n-1;
int first=-1,end=-1; //注意点1:直接把first和end设置为-1如果没有nums[mid]==target可以直接返回-1更加方便。
while(left<=right){
int mid = (left+right)/2;
if(nums[mid]==target){
first = mid; //用first和end来记录mid
right = mid-1; //重点1:想获得最左侧边界的值,让right指针为mid-1,直接在左半部分接着搜索。
}else if(nums[mid]<target){
left = mid+1;
}else{
right = mid-1;
}
}
left=0;right=n-1;
while(left<=right){
int mid = (left+right)/2;
if(nums[mid]==target){
end = mid;
left = mid+1; //重点1:想获得最右侧边界的值,让left指针为mid+1,直接在右半部分接着搜索。
}else if(nums[mid]<target){
left = mid+1;
}else{
right = mid-1;
}
}
return new int[]{first,end};
}
}
19、比较版本号(中-高)
易错点1:"."代表任意字符,要加转义字符"\\."
代码:
class Solution {
public int compareVersion(String version1, String version2) {
String[] str1 = version1.split("\\."); //易错点1:"."代表任意字符,要加转义字符"\\."
String[] str2 = version2.split("\\.");
int n1 = str1.length;
int n2 = str2.length;
int i=0,j=0;
while(i<n1 || j<n2){
int x = i<n1 ? toNumber(str1[i]) : 0; //重点:使用三目运算符来简化对长度的处理
int y = j<n2 ? toNumber(str2[j]) : 0;
i++;j++;
if(x<y) return -1;
else if(x>y) return 1;
else continue;
}
return 0;
}
public int toNumber(String s){
int n = s.length();
int res = 0;
for(int i=0;i<n;i++){ //易错点1:从高位往低位走
res = res*10 + (s.charAt(i)-'0');
}
return res;
}
}
20、排序数组(手撕快速排序)(中-高)
pivot:中心点,轴心
partition:分割,划分
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums,0,nums.length-1);
return nums;
}
public void quickSort(int[] nums,int begin,int end){
if(begin>=end) return; //易错1:易漏
int pivot = partition(nums,begin,end);
quickSort(nums,begin,pivot-1);
quickSort(nums,pivot+1,end);
}
public int partition(int[] nums,int begin,int end){
int index = nums[begin]; //取begin为index的下标
int left = begin+1,right = end; //begin要+1,如果取end为index下标,right要-1
while(left<=right){
while(left<=right && nums[left]<index){
left++;
}
while(left<=right && nums[right]>index){
right--;
}
if(left<=right){ //再次判断left<=right
swap(nums,left,right);
left++;
right--;
}
}
swap(nums,right,begin); //交换index的下标begin和right,如果index下标为end要交换left
return right; //如果取begin为index的下标返回right,如果取end为index下标返回left
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
21、最长回文子串(中-高)
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
if(n<=1) return s;
int start=0,end=0;
for(int i=0;i<n-1;i++){
int len1 = getSubStr(s,i,i);
int len2 = getSubStr(s,i,i+1);
int len = Math.max(len1,len2);
if(len>end-start){
start = i-(len-1)/2;
end = i+len/2;
}
}
return s.substring(start,end+1);
}
public int getSubStr(String s,int left,int right){
while(left>=0 && right<=s.length()-1 && s.charAt(left)==s.charAt(right)){
left--;
right++;
}
return right-left-1;
}
}
22、全排列(中-中)
class Solution {
List<List<Integer>> list = new ArrayList<>();
Deque<Integer> deque = new ArrayDeque<>();
int[] flag;
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
flag = new int[n+1];
dfs(nums,0);
return list;
}
public void dfs(int[] nums,int cnt){
if(cnt==nums.length){
list.add(new ArrayList<>(deque));
}
for(int i=0;i<nums.length;i++){
if(flag[i]==0){
deque.addLast(nums[i]);
flag[i]=1;
dfs(nums,cnt+1);
flag[i]=0;
deque.removeLast();
}
}
}
}
23、最长递增子序列(中-中)
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if(n==0) return 0;
int maxx = 1;
int[] dp = new int[n+1];
for(int i=0;i<n;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
dp[i] = Math.max(dp[j]+1,dp[i]);
}
maxx = Math.max(dp[i],maxx);
}
}
return maxx;
}
}
24、二叉树的右视图(中-中)
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root==null) return res;
Deque<TreeNode> deque = new ArrayDeque<>();
deque.offer(root);
while(!deque.isEmpty()){
int n = deque.size();
for(int i=0;i<n;i++){
TreeNode x = deque.poll();
if(i==n-1) res.add(x.val);
if(x.left!=null){
deque.offer(x.left);
}
if(x.right!=null){
deque.offer(x.right);
}
}
}
return res;
}
}
25、用栈实现队列(中-低)
实现思路:设置两个栈,第1个栈用于装入新元素,第2个栈用于将第1个栈的元素逆序,从而形成队列。
只有在第2个栈为空的情况下才需要重新装入元素,不然直接取出(第2个栈中的)元素即可。
class MyQueue {
//两个栈,一个栈装新元素,一个栈负责倒出来逆序
public Deque<Integer> deque ;
public Deque<Integer> res;
public MyQueue() {
deque = new ArrayDeque<>();
res = new ArrayDeque<>();
}
public void push(int x) {
deque.push(x);
}
public int pop() {
if(res.isEmpty()){ //只有在第2个栈没元素的前提下才需要重新装入元素,不然直接取即可。
while(!deque.isEmpty()){
res.push(deque.pop());
}
}
return res.pop();
}
public int peek() {
if(res.isEmpty()){
while(!deque.isEmpty()){
res.push(deque.pop());
}
}
return res.peek();
}
public boolean empty() {
return deque.isEmpty() && res.isEmpty();
}
}
26、删除链表中重复元素(中-低-原理不理解)
易错点1:p.next也要不为null
重点1:不相等时,取消对相等元素的链接
重点2:相等时,直接移动到下一个节点
代码:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null) return null;
ListNode dummy = new ListNode(-1000,head);
ListNode p = dummy;
while(p!=null && p.next!=null){ //易错点1:p.next也要不为null
if(p.val==p.next.val){ //重点1:不相等时,取消对相等元素的链接
p.next = p.next.next;
}else{ //重点2:相等时,直接移动到下一个节点
p = p.next;
}
}
return head;
}
}
27、删除链表中重复元素II(中-低)
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummyNode = new ListNode(-1000,head);
ListNode p = dummyNode;
while(p.next!=null && p.next.next!=null){
if(p.next.val != p.next.next.val){
p = p.next;
}else{
//重点1:关键思路是如果cur的下个和下下个元素相等,代表有重复元素,记录下个元素的值,然后不停地往后推移指针,直到找到第一个不同元素的值,让p的下一个节点指向第一个不同元素值的节点
ListNode difNode = p.next;
while(difNode!=null && difNode.val == p.next.val){
difNode = difNode.next;
}
p.next = difNode;
}
}
return dummyNode.next;
}
}
28、反转链表II(中-低)
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(0,head);
ListNode pre = dummy,rightNode = dummy;
for(int i=0;i<left-1;i++){
pre = pre.next;
}
ListNode leftNode = pre.next;
for(int j=0;j<right;j++){
rightNode = rightNode.next;
}
ListNode after = rightNode.next;
pre.next=null; //易错点1:要让pre、rightNode先指向空
rightNode.next=null;
reverseNode(leftNode);
pre.next = rightNode; //易错点2:因为链表指向反转,所以pre的下一个节点要指向反转链表的最右节点,leftNode的下一个节点要指向after。
leftNode.next = after;
return dummy.next;
}
public ListNode reverseNode (ListNode head){
ListNode prev = null;
ListNode cur = head;
while(cur!=null){
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
}
29、岛屿数量(中-低)
class Solution {
public int line;
public int row;
public int numIslands(char[][] grid) {
line = grid.length;
row = grid[0].length;
int num=0;
for(int i=0;i<line;i++){
for(int j=0;j<row;j++){
if(grid[i][j]=='1'){
num++;
dfs(grid,i,j);
}
}
}
return num;
}
public void dfs(char[][] grid,int i,int j){
if(i<0 || i>= line || j<0 || j>= row || grid[i][j]=='0'){
return;
}
grid[i][j]='0';
dfs(grid,i+1,j);
dfs(grid,i-1,j);
dfs(grid,i,j+1);
dfs(grid,i,j-1);
}
}
30、二叉树的层序遍历(中-低)
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if(root==null) return list;
Deque<TreeNode> deque = new ArrayDeque<>();
deque.offer(root);
while(!deque.isEmpty()){
int n = deque.size();
List<Integer> l = new ArrayList<>();
for(int i=0;i<n;i++){
TreeNode x = deque.poll();
l.add(x.val);
if(x.left!=null){
deque.offer(x.left);
}
if(x.right!=null){
deque.offer(x.right);
}
}
list.add(l);
}
return list;
}
}
31、有效的括号(中-低)
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new ArrayDeque<>();
int n = s.length();
for(int i=0;i<n;i++){
char x = s.charAt(i);
if(x=='(' || x=='[' || x=='{'){
deque.push(x);
continue;
}
if(deque.isEmpty()){
return false;
}
char y = deque.pop();
if(!(y=='(' && x==')' || y=='[' && x==']' || y=='{' && x=='}')){
return false;
}
}
return deque.isEmpty();
}
}
32、删除链表的导数第N个结点(低-中)
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy , slow = dummy;;
for(int i=0;i<=n;i++){
fast = fast.next;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
33、盛水最多的容器(中-低)
class Solution {
public int maxArea(int[] height) {
int n = height.length;
int left=0,right=n-1;
int maxWater = 0;
while(left<right){
maxWater=Math.max((right-left)*Math.min(height[right],height[left]),maxWater);
if(height[left]<=height[right]){ //哪边矮就移动指针,双指针哪边挫(内)卷哪边
left++;
}else{
right--;
}
}
return maxWater;
}
}
34、字符串相加(易-高)
class Solution {
public String addStrings(String num1, String num2) {
int n1 = num1.length(),n2 = num2.length();
int i=n1-1,j=n2-1,add = 0;
StringBuffer buf = new StringBuffer();
while(i>=0 || j>=0){
int x = i>=0 ? num1.charAt(i)-'0' : 0;
int y = j>=0 ? num2.charAt(j)-'0' : 0;
i--;j--;
int left = (x+y+add)%10;
add = (x+y+add)/10;
buf.append(left);
}
if(add>0) buf.append(add);
buf.reverse();
return buf.toString();
}
}
35、最小栈(易-高)
class MinStack {
Deque<Integer> deque;
Deque<Integer> minDeque;
public MinStack() {
deque = new ArrayDeque<>();
minDeque = new ArrayDeque<>();
minDeque.push(Integer.MAX_VALUE);
}
public void push(int x) {
deque.push(x);
minDeque.push(Math.min(x,minDeque.peek()));
}
public void pop() {
deque.pop();
minDeque.pop();
}
public int top() {
return deque.peek();
}
public int getMin() {
return minDeque.peek();
}
}
36、X的平方根(易-高)
class Solution {
public int mySqrt(int x) {
if(x==0) return 0; //小心x为0,为导致除0异常
int left = 0,right = x,ans=0;
while(left<=right){
int mid = (left - right)/2 + right; //避免超界
if(mid==x/mid) return mid;
else if(mid<x/mid){
ans=mid;
left=mid+1;
}else{
right=mid-1;
}
}
return ans;
}
}
37、反转链表 (易-高)
public class Receive {
public static class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val){this.val = val;}
ListNode(int val,ListNode next){
this.val = val;
this.next = next;
}
}
public static ListNode dummyNode = new ListNode(1); //虚拟头结点
public static void initList(){
int[] arr = {1,2,3,4,5};
ListNode p = dummyNode;
for(int i=0;i<arr.length;i++){
ListNode q = new ListNode(arr[i]);
p.next = q;
p = q;
}
}
public static void main(String[] args){
initList(); //生成一个链表,虚拟头结点dymmyNode
ListNode head = reverseList(dummyNode.next); //将真正节点逐一反转,最后会返回逆转后的头
printList(head);
}
public static void printList(ListNode head){
while(head!=null){
System.out.println(head.val);
head = head.next;
}
}
public static ListNode reverseList(ListNode head){
ListNode prev = null;
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
}
38、无重复字符的最长子串(易-中)
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
Set<Character> set = new HashSet<>();
int right=0,left=0;
int maximum = 0;
while(right<n){
char x = s.charAt(right);
if(!set.contains(x)){
set.add(x);
right++;
}else{
set.remove(s.charAt(left)); //关键在于此,收缩左边界是删除left下标位置的元素,直到不包含这个重复元素
left++;
}
maximum = Math.max(maximum,right-left);
}
return maximum;
}
}
39、买卖股票最佳时机(易-中)
class Solution {
int maxx = -Integer.MAX_VALUE;
int minn = Integer.MAX_VALUE;
public int maxProfit(int[] prices) {
int n = prices.length;
for(int i=0;i<n;i++){
minn = Math.min(minn,prices[i]);
maxx = Math.max(maxx,prices[i]-minn);
}
return maxx;
}
}
40、两数之和(易-低)
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
Map<Integer,Integer> map = new HashMap<>();
for(int i=0;i<n;i++){
if(map.containsKey(target-nums[i])){
return new int[]{map.get(target-nums[i]),i};
}
map.put(nums[i],i);
}
return new int[]{0};
}
}
41、相交链表(易-低)
public class Solution {
public Set<ListNode> set = new HashSet<>();
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p = headA;
while(p!=null){
set.add(p);
p=p.next;
}
ListNode q = headB;
while(q!=null){
if(set.contains(q)){
return q;
}
q = q.next;
}
return null;
}
}
42、环形链表(易-低)
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head , slow = head;
while(fast != null && fast.next != null){
if(fast==null) return false;
slow = slow.next;
fast = fast.next.next;
if(fast==slow) return true;
}
return false;
}
}
43、环形链表II(易-低)
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode p = head;
Set<ListNode> set = new HashSet<>();
while(p!=null){
if(set.contains(p)){
return p;
}
set.add(p);
p=p.next;
}
return null;
}
}
44、二分查找(易-低)
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0 , right = n-1;
while(left<=right){
int mid = (left-right)/2+right;
if(nums[mid]==target) return mid;
else if(nums[mid]<target){
left = mid+1;
}else{
right = mid-1;
}
}
return -1;
}
}
45、爬楼梯(易-低)
class Solution {
public int climbStairs(int n) {
int[] dp = new int[47];
dp[1]=1;dp[2]=2;
for(int i=3;i<=45;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
46、二叉树的中序遍历(易-低)
class Solution {
public List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null) return res;
inorderTraversal(root.left);
res.add(root.val);
inorderTraversal(root.right);
return res;
}
}
47、最大子数组和(中-低)
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
int maxAns = nums[0]; //maxAns要为首元素
int[] dp = new int[n+1];
dp[0] = nums[0];
for(int i=1;i<n;i++){
dp[i] = Math.max(nums[i]+dp[i-1],nums[i]);
maxAns = Math.max(maxAns,dp[i]); //重点:要记录最大值
}
return maxAns;
}
}
48、括号生成(中-高)
class Solution {
public List<String> ans = new ArrayList<>();
public List<String> generateParenthesis(int n) {
backTrack(new StringBuffer(),0,0,n);
return ans;
}
public void backTrack(StringBuffer buf,int left,int right,int n){
if(left+right==2*n){
ans.add(buf.toString());
return;
}
if(left<n){
buf.append('(');
backTrack(buf,left+1,right,n);
buf.deleteCharAt(buf.length()-1);
}
if(right<left){ //重点1:有括号数量少于左括号数量,可以放一个右括号
buf.append(')');
backTrack(buf,left,right+1,n);
buf.deleteCharAt(buf.length()-1); //重点2:StringBuffer删除末尾元素用的是deleteCharAt
}
}
}
49、两数相加(低-高)
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int carry = 0;
ListNode dummyNode = new ListNode(-1);
ListNode p = dummyNode;
while(l1!=null || l2!=null){
int num1 = l1 != null ? l1.val : 0 ;
int num2 = l2 != null ? l2.val : 0 ;
int num = num1 + num2 + carry;
int left = num%10;
carry = num/10;
ListNode newNode = new ListNode(left);
p.next = newNode;
p = p.next;
l1 = l1 != null ? l1.next:l1 ;
l2 = l2 != null ? l2.next:l2 ;
}
if(carry!=0){
p.next = new ListNode(carry);
}
return dummyNode.next;
}
}
50、LRU缓存(难-中-易错)
解法1:使用HashMap+ArrayDeque来实现。
考虑的点:
1、获取元素时,如果存在元素,要将元素设置为最近使用的;如果不存在元素,则返回-1。
2、插入元素时,需要判断元素的key是否已经存在,如果存在需要删除key,然后重新设置key和新值。
3、插入元素时,如果缓存容量已满,要删除最久未使用的键。
存在的问题:会超时。在 ArrayDeque
中,remove(Object)
方法的时间复杂度是 O(n),因为它需要遍历整个队列来找到并移除指定的元素。当数据量较大时,这种线性时间复杂度的操作会导致性能下降,进而导致超时。
重点1:如果键已经存在,必须删除键,后面重新设置更新值,这种情况容易忽略。
import java.util.*;
class LRUCache {
private final Map<Integer, Integer> hash;
private final Deque<Integer> deq;
private final int capacity;
public LRUCache(int capacity) {
this.hash = new HashMap<>();
this.deq = new ArrayDeque<>();
this.capacity = capacity;
}
public int get(int key) {
if (hash.containsKey(key)) {
deq.remove(key);
deq.addLast(key);
return hash.get(key);
} else {
return -1;
}
}
public void put(int key, int value) {
// 如果键已经存在,更新值并调整其在队列中的位置
if (hash.containsKey(key)) { //重点1:如果键已经存在,必须删除键,后面重新设置更新值,这种情况容易忽略
deq.remove(key);
} else if (deq.size() == capacity) {// 容量达到上限,删除最久未使用的键
int oldestKey = deq.removeFirst();
hash.remove(oldestKey);
}
deq.addLast(key);
hash.put(key, value);
}
}
解法2:双向链表+哈希表(官方解法)
class LRUCache {
public class DLinkedNode{ //注意1:单向链表ListNode,双向链表:DLinkedNode
int key;
int val;
DLinkedNode pre;
DLinkedNode next;
public DLinkedNode(){} //注意2:加一个无参构造方法
public DLinkedNode(int key,int val){
this.key = key;
this.val = val;
}
}
public Map<Integer,DLinkedNode> cache; //重点1:哈希表值的位置是整个双向链表节点。取名为cache
DLinkedNode head,tail;
public Integer capacity;
//问题1:如何快速定位到某个key在链表中对应的元素?
//解决思路:利用哈希表,键存key,值存整个链表节点
public LRUCache(int capacity) {
cache = new HashMap<>();
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.pre = head;
this.capacity = capacity;
}
//删除某个元素(使用场景:1、元素超过缓存容量上限。2、访问元素后要移动时)
public void removeNode(DLinkedNode node){
node.pre.next = node.next;
node.next.pre = node.pre;
}
//将元素移动到首位(使用场景:1、访问元素后要移动元素到首位)
public void moveToHead(DLinkedNode node){
removeNode(node);
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre=head;
}
//添加新元素(使用场景:1、新的元素添加)
public void addToHead(DLinkedNode node){
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre=head;
}
public int get(int key) {
//缓存中有key,要提升优先级,把结点移动到链表头
if(cache.containsKey(key)){
DLinkedNode node = cache.get(key);
moveToHead(node);
return node.val;
}else{
return -1;
}
}
//问题2:如何知道要删除元素的key?
//解决思路:在链表结点中存key值
public void put(int key, int value) {
if(cache.containsKey(key)){
//缓存中有元素,要更新元素值,移动到链表头
DLinkedNode node = cache.get(key);
node.val=value;
moveToHead(node);
}else{
DLinkedNode newNode = new DLinkedNode(key,value);
//缓存中无元素,容量达到上限,要删除链表尾元素,腾出空间,删除缓存(哈希表)中的元素
if(cache.size()==capacity){
DLinkedNode lastNode = tail.pre;
removeNode(lastNode);
cache.remove(lastNode.key);
}
//添加元素到链表头,添加元素到缓存
addToHead(newNode);
cache.put(key,newNode);
}
}
}
51、把字符串转换成整数(难-低-原理不清)
class Solution {
public int myAtoi(String str) {
int n = str.length(),sign = 1, i=0,ans=0,temp = Integer.MAX_VALUE/10;
if(n==0) return 0;
while(str.charAt(i)==' '){
if(++i==n) return 0; //重点1:双重效果,一遍递增,一遍判断是否全为空格
}
if(str.charAt(i)=='-') sign = -1;
if(str.charAt(i)=='-' || str.charAt(i)=='+') i++; //重点2:双重效果,过滤第一个符号后的符号,让i++
for(int j=i;j<n;j++){
if(str.charAt(j)>'9' || str.charAt(j)<'0') break; //易错1:注意括号内是j
if(ans>temp || ans==temp && str.charAt(j)>'7'){
//重点3:str.charAt(j)是0到9之间的数
//如果ans>temp,ans*10>temp*10==Integer.MAX_VALUE
//如果ans==temp,ans*10==temp*10==Integer.MAX_VALUE
//所以不能继续往下执行,其中如果ans为temp时,要看最后一位是否大于7
return sign==1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
ans = ans*10+(str.charAt(j)-'0');
}
return ans*sign;
}
}
52、零钱兑换(难-低-思路缺乏)
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
for(int i=0;i<amount+1;i++){ //易错点1:要让整个dp数组都被赋值为最大值
dp[i] = amount+1; //易错点2:最大值是amount+1,永远不可能取到
}
dp[0]=0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(coins[j]<=i){ //易错点3:保证数组不越界
//重点1:没办法被凑的就会变成最大值,能被凑的就会有值,
dp[i] = Math.min(dp[i],dp[i-coins[j]]+1); //易错点4:不是dp[amount-coins[j]]+1而是dp[i-coins[j]]+1
}
}
}
return dp[amount]>amount ? -1 : dp[amount];
}
}
53、合并两个有序数组(中-低)
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//重点1:在计算时间复杂度时会忽略常数因子,比如O(9m+9n)的时间复杂度都是O(m+n)
int[] sorted = new int[m+n+1];
int i=0,j=0,temp=0;
while(i<m || j<n){
if(i==m){
sorted[i+j] = nums2[j++];
}else if(j==n){
sorted[i+j] = nums1[i++];
}else if(nums1[i]<=nums2[j]){
sorted[i+j] = nums1[i++];
}else{
sorted[i+j] = nums2[j++];
}
}
for(int k=0;k<m+n;k++){
nums1[k] = sorted[k];
}
}
}
54、合并两个有序链表(中-中)
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummyNode = new ListNode(-1);
ListNode p = dummyNode;
while(list1 != null && list2 != null){
if(list1.val<=list2.val){
p.next = list1; //优化点1:不用新建一个节点,直接让p.next指向新节点
list1 = list1.next;
}else{
p.next= list2;
list2 = list2.next;
}
p = p.next;
}
p.next = list1 != null ? list1 : list2; //最后直接把剩下的链表串上即可
return dummyNode.next;
}
}
55、字符串相乘(中-低)
class Solution {
public String multiply(String num1, String num2) {
int n1 = num1.length();
int n2 = num2.length();
if(num1.equals("0") || num2.equals("0") return "0";
int[] arr = new int[n1+n2];
for(int i=n1-1;i>=0;i--){
for(int j=n2-1;j>=0;j--){
arr[i+j+1] += (num1.charAt(i)-'0') * (num2.charAt(j)-'0');
}
}
int carry = 0;
for(int k = n1+n2-1;k>0;k--){ //注意
int temp = arr[k] + carry;
arr[k] = temp%10;
carry = temp/10;
}
StringBuffer buf = new StringBuffer();
if(carry!=0){
buf.append(carry);
}
for(int i=1;i<n1+n2;i++){
buf.append(arr[i]);
}
return buf.toString();
}
}
56、从前序与中序遍历构造二叉树(高-低)
二叉树性质:
1、中序和前序遍历结果的长度相等
2、前序遍历构成:[ 当前节点 + 左子树遍历 + 右子树遍历 ]
3、中序遍历构成:[ 左子树遍历 + 当前节点 + 右子树遍历 ]
4、当前节点 + 左子树遍历 = 左子树遍历 + 当前节点
思路:使用递归,先从前序遍历的左子树中取出一个节点,这个节点就是每次递归的根节点,最后返回的也是这个根节点。然后看当前节点在中序遍历中的位置,通过这个位置可以计算出左子树的长度,从而得知前序遍历的左子树构建的末节点在哪。
为了避免每次都扫描一遍中序遍历的数组,可以先将中序结果存储在一个哈希表中,以节点值为键,以数组下标为值,方便快速查找到下标。
preStart的作用是每次后移一位,取下一个节点。preEnd的作用是判断是否已经构造完毕。inStart的作用是和inRoot判断左子树的长度。inEnd没啥作用,纯粹就是标识中序最后一个节点。
class Solution {
Map<Integer,Integer> hash = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
for(int i=0;i<n;i++){
hash.put(inorder[i],i);
}
return buildTree(preorder,0,n-1,0,n-1);
}
public TreeNode buildTree(int[] preorder,int preStart,int preEnd,int inStart,int inEnd){
if(preStart>preEnd){
return null;
}
TreeNode root = new TreeNode(preorder[preStart]);
int inRoot = hash.get(preorder[preStart]);
int len = inRoot - inStart;
root.left = buildTree(preorder,preStart+1,preStart+len,inStart,inRoot-1);
root.right = buildTree(preorder,preStart+len+1,preEnd,inRoot+1,inEnd);
return root;
}
}
57、对称二叉树(中-低)
思路:优先判断不对称,后判断对称。因为如果先判断对称会导致还没遍历完就提前返回对称,造成误判。
重点1:均为null一般是到了叶子节点,此时返回true不会有问题,会返回上一层
重点2:变量必须写if(left==null),如果是调用方法可以省略==null,用!代表非。
class Solution {
public boolean isSymmetric(TreeNode root) {
return isSymmetric(root.left,root.right);
}
public boolean isSymmetric(TreeNode left,TreeNode right){
//本题思路是优先判断不对称,后判断对称。因为如果先判断对称会导致还没遍历完就提前返回对称,造成误判
if(left==null && right==null) return true; //重点1:均为null一般是到了叶子节点,此时返回true不会有问题,会返回上一层
if(left==null || right==null) return false;
if(left.val!=right.val) return false;
if( !isSymmetric(left.left,right.right) || !isSymmetric(left.right,right.left)){ //重点2:变量必须写if(left==null),如果是调用方法可以省略==null,用!代表非。
return false;
}
return true;
}
}
58、旋转图像(易-高)
class Solution {
public void rotate(int[][] matrix) {
int row = matrix.length;
int column = matrix[0].length;
for(int i=0;i<row/2;i++){
for(int j=0;j<column;j++){
swap(matrix,i,j,row-i-1,j);
}
}
for(int i=0;i<row;i++)
for(int j=0;j<i;j++){
swap(matrix,i,j,j,i);
}
}
public void swap(int[][] matrix,int a,int b,int c,int d){
int temp = matrix[a][b];
matrix[a][b] = matrix[c][d];
matrix[c][d] = temp;
}
}
59、验证二叉搜索树(难-中-原理不清)
思路:对[5,4,6,null,null,3,7]来说,3这个点应该出现在搜索树左侧,如何识别出这个false。靠的是lower记录下限,upper记录上限,右子树不能高于上限,左子树不能低于下限。
往左子树方向,更新上限,因为对左子树来说,父节点更大。往右子树方向,更新下限,因为对右子树来说,父节点更小。
重点1:不能是Integer的最大和最小值,因为极端值2147483647=2^31-1等于Integer.MAX_VALUE会导致返回错误,事实上这个答案是可以的。
重点2:如果是左子树,只和upper比。如果是右子树,之和lower比。
易错点1:注意lower和upper的顺序,什么时候用root.val替代
class Solution {
public boolean isValidBST(TreeNode root) {
return dfs(root,Long.MAX_VALUE,Long.MIN_VALUE); //重点1:不能是Integer的最大和最小值,因为极端值2147483647=2^31-1等于Integer.MAX_VALUE会导致返回错误,事实上这个答案是可以的。
}
public boolean dfs(TreeNode root,long upper,long lower){
if(root==null) return true;
if(root.val<=lower || root.val>=upper){//重点2:如果是左子树,只和upper比。如果是右子树,之和lower比。
return false;
}
return dfs(root.left,root.val,lower) && dfs(root.right,upper,root.val); //易错点1:注意lower和upper的顺序,什么时候用root.val替代
}
//对[5,4,6,null,null,3,7]来说,3这个点应该出现在搜索树左侧,如何识别出这个false。靠的是lower记录下限,upper记录上限,右子树不能高于上限,左子树不能低于下限。往左子树方向,更新上限,因为对左子树来说,父节点更大。往右子树方向,更新下限,因为对右子树来说,父节点更小。
}
60、最长有效括号(难-低-原理不清)
class Solution {
public int longestValidParentheses(String s) {
Deque<Integer> deq = new ArrayDeque<>();
deq.push(-1);
int n = s.length();
int maxAns = 0;
for(int i=0;i<n;i++){
char x = s.charAt(i);
if(x=='('){
deq.push(i);
}else{
deq.pop();
if(!deq.isEmpty()){
maxAns = Math.max(maxAns,i-deq.peek());
}else{ //当栈为空时,入栈当前的索引是为了记录无效括号的位置,方便后续计算有效长度。
deq.push(i);
}
}
}
return maxAns;
}
}
61、最长递增子序列(难-中-原理不清)
二分查找的目的是找到第一个比nums[i]小的数的下标pos,然后将dp[pos+1]设置为nums[i]
重点1:len的初始值为1
重点2:这里不是if(nums[i]<=dp[mid]),而是dp[mid]<nums[i],因为要找到的是dp中第一个比nums[i]小的数
重点3:返回的不是dp的长度,而是len的长度
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if(n==0) return 0;
int[] dp = new int[n+1];
int len = 1; //重点1:len的初始值为1
dp[len] = nums[0];
for(int i=1;i<n;i++){
if(nums[i]>dp[len]){
dp[++len] = nums[i];
}else{
//二分查找的目的是找到第一个比nums[i]小的数的下标pos,然后将dp[pos+1]设置为nums[i]
int left=1,right=len,pos=0;
while(left<=right){
int mid = (left+right)/2;
if(dp[mid]<nums[i]){ //重点2:这里不是if(nums[i]<=dp[mid]),而是dp[mid]<nums[i],因为要找到的是dp中第一个比nums[i]小的数
left = mid+1;
pos = mid;
}else{
right = mid-1;
}
}
dp[pos+1] = nums[i];
}
}
return len; //重点3:返回的不是dp的长度,而是len的长度
}
}
62、子集(中-低)
重点1:生成子集与生成全排列的不同之处在于:不需要一个sign数组来记录哪些元素被访问过,只需要用currentIndex来记录下标,在for循环中每次从currentIndex开始,保证元素不重复。
重点2:这里currentIndex要变成i+1
class Solution {
int n = 0;
List<List<Integer>> subsetsList = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
n = nums.length;
for(int i=0;i<=n;i++){
generateSubsets(nums,i,0,new ArrayDeque<>(),0);
}
return subsetsList;
}
//重点1:生成子集最大的不同就是用now来记录当前的数字下标,往后的每次dfs都是从当前数字下标开始
public void generateSubsets(int[] nums,int targetSize,int currentSize,Deque<Integer> currentSubset,int currentIndex){
if(targetSize==currentSize){
subsetsList.add(new ArrayList<>(currentSubset));
return;
}
for(int i=currentIndex;i<n;i++){
currentSubset.addLast(nums[i]);
generateSubsets(nums,targetSize,currentSize+1,currentSubset,i+1); //重点2:这里currentIndex要变成i+1
currentSubset.removeLast();
}
}
}
63、最小覆盖子串(难-高-思路不清晰)
思路:滑动窗口,如果滑动窗口内的元素完全包含t字符串中的字符,可以收缩左边界。判断是否完全包含的方法是调用check方法。check方法会把ori哈希表中的元素逐一取出,判断cnt中这些元素的数量是否>=ori中元素数量(大于是可以的)。所以需要2个哈希表,ori表用来记录t字符串中各个元素出现次数,cnt表用来记录滑动窗口内各个元素的出现次数。
重点1:把right指针所指的元素逐一放入cnt哈希表中,统计个数。
重点2:left要<=right的原因是要考虑仅有一个元素的情况。
重点3:因为substring函数是左闭右开,所以要往后加一位。
重点4:哈希表需要用迭代器来遍历。
重点5:必须是小于,如果cnt中元素数量大于ori中要求的数量,这种也是符合的。
class Solution {
Map<Character,Integer> ori = new HashMap<>();
Map<Character,Integer> cnt = new HashMap<>();
//思路:滑动窗口,如果滑动窗口内的元素完全包含t字符串中的字符,可以收缩左边界。判断是否完全包含的方法是调用check方法。check方法会把ori哈希表中的元素逐一取出,判断cnt中这些元素的数量是否>=ori中元素数量(大于是可以的)。所以需要2个哈希表,ori表用来记录t字符串中各个元素出现次数,cnt表用来记录滑动窗口内各个元素的出现次数。
public String minWindow(String s, String t) {
int ns = s.length(),nt = t.length();
int left = 0,right = -1,minLen=Integer.MAX_VALUE;
int ansLeft = -1,ansRight = -1;
for(int i=0;i<nt;i++){
char x = t.charAt(i);
ori.put(x,ori.getOrDefault(x,0)+1);
}
while(right<ns){
++right;
if(right<ns && ori.containsKey(s.charAt(right))){ //重点1:把right指针所指的元素逐一放入cnt哈希表中,统计个数。
cnt.put(s.charAt(right),cnt.getOrDefault(s.charAt(right),0)+1);
}
while(check() && left<=right){ //重点2:left要<=right的原因是要考虑仅有一个元素的情况
if(right-left<minLen){
minLen = right-left;
ansLeft = left;
ansRight = left+minLen+1; //重点3:因为substring函数是左闭右开,所以要往后加一位
}
if(ori.containsKey(s.charAt(left))){
cnt.put(s.charAt(left),cnt.getOrDefault(s.charAt(left),0)-1);
}
left++;
}
}
return minLen==Integer.MAX_VALUE ? "":s.substring(ansLeft,ansRight);
}
public boolean check(){
Iterator iter = ori.entrySet().iterator(); //重点4:哈希表需要用迭代器来遍历
while(iter.hasNext()){
Map.Entry entry = (Map.Entry)iter.next();
Character key = (Character)entry.getKey();
Integer value = (Integer)entry.getValue();
if(cnt.getOrDefault(key,0)<value) return false; //重点5:必须是小于,如果cnt中元素数量大于ori中要求的数量,这种也是符合的。
}
return true;
}
}
64、字符串中单词翻转(易-中)
class Solution {
public String reverseMessage(String message) {
int n = message.length();
List<String> list = new ArrayList<>();
for(int i=0;i<n;i++){
if(i<n && message.charAt(i)!=' '){
int j = i;
while(j<n && message.charAt(j)!=' '){
j++;
}
list.add(message.substring(i,j));
i = j;
}
}
StringBuffer buf = new StringBuffer();
Collections.reverse(list);
for(int i=0;i<list.size();i++){
buf.append(list.get(i));
if(i!=list.size()-1) buf.append(' ');
}
return buf.toString();
}
}
65、回文链表(中-低)
重点1:翻转链表后返回的其实是原先的末节点,此时第1段链表最后的指针还是没置为空,因此不能用head!=null来判断执行的条件,但是第二段指针的末尾指向空,可以利用这点来控制最终的结束。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if(head==null || head.next ==null) return true;
ListNode slow = head,fast = head;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
ListNode secondHead = reverseList(slow);
while(secondHead!=null){ //重点1:翻转链表后返回的其实是原先的末节点,此时第1段链表最后的指针还是没置为空,因此不能用head!=null来判断执行的条件,但是第二段指针的末尾指向空,可以利用这点来控制最终的结束。
if(head.val!=secondHead.val) return false;
head = head.next;
secondHead = secondHead.next;
}
return true;
}
public ListNode reverseList(ListNode head){
ListNode prev = null;
ListNode cur = head;
while(cur!=null){
ListNode nex = cur.next;
cur.next = prev;
prev = cur;
cur = nex;
}
return prev;
}
}
66、平衡二叉树(难-低-原理不理解)
平衡二叉树定义:对【每一个节点】来说,左右子树深度之差不超过1(<=1)。
重点1:要加上leftHeight==-1和rightHeight==-1这个条件,因为这个是用来判断深层子树是否也符合平衡二叉树的条件,从而让这个条件不断上浮(向上传递)的实现手段。
重点2:如果某个节点的左右子树符合平衡二叉树条件,返回最深的节点值+1。
class Solution {
//平衡二叉树定义:对【每一个节点】来说,左右子树深度之差不超过1(<=1)。
public boolean isBalanced(TreeNode root) {
return height(root)>=0;
}
public int height(TreeNode root){
if(root==null) return 0;
int leftHeight = height(root.left);
int rightHeight = height(root.right);
//重点1:要加上leftHeight==-1和rightHeight==-1这个条件,因为这个是用来判断深层子树是否也符合平衡二叉树的条件,从而让这个条件不断上浮(向上传递)的实现手段。
if(leftHeight==-1 || rightHeight==-1 || Math.abs(rightHeight-leftHeight)>1){
return -1;
}else{
return Math.max(leftHeight,rightHeight)+1; //重点2:如果某个节点的左右子树符合平衡二叉树条件,返回最深的节点值+1
}
}
}
67、最长连续序列(难-低-思路难想)
重点1:直接for循环就可以遍历number。
重点2:避免出现O(n²)的情况,必须跳过一些东西,跳过的条件是num-1存在于set集合中。
重点3:从最小的数向上尝试,维护len和num的值,通过maxLen记录每次最长的长度。
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> number = new HashSet<>();
for(int num : nums){
number.add(num);
}
int len = 1;
int maxLen = 0;
for(Integer num : number){ //重点1:直接for循环就可以遍历number
if(number.contains(num-1)) continue; //重点2:避免出现O(n²)的情况,必须跳过一些东西,跳过的条件是num-1存在于set集合中
len = 1;
while(number.contains(num+1)){ //重点3:从最小的数向上尝试,维护len和num的值,通过maxLen记录每次最长的长度
len++;
num = num+1;
}
maxLen = Math.max(len,maxLen);
}
return maxLen;
}
}
68、路径总和II(中-低)
重点1:引用类型不需要跟着dfs函数传递
重点2:通过节点为null的判断可以省略掉root.left==null和root.right==null的判断
class Solution {
List<List<Integer>> ans = new ArrayList<>();
Deque<Integer> path = new ArrayDeque<>(); //重点1:引用类型不需要跟着dfs函数传递
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root,targetSum);
return ans;
}
public void dfs(TreeNode node,int targetSum){
if(node==null) return; //重点2:通过节点为null的判断可以省略掉root.left==null和root.right==null的判断
path.addLast(node.val);
targetSum -= node.val;
if(node.left==null && node.right==null && targetSum==0){
ans.add(new ArrayList<>(path));
}
dfs(node.left,targetSum);
dfs(node.right,targetSum);
path.removeLast();
return;
}
}
69、最小路径和(易-高)
思路:动态规划,填充第一行和第一列,然后使用这个递推式:dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+ grid[i][j];
class Solution {
public int minPathSum(int[][] grid) {
int row = grid.length;
int column = grid[0].length;
int[][] dp = new int[row+1][column+1];
dp[0][0] = grid[0][0];
for(int i=1;i<row;i++) dp[i][0] = grid[i][0]+dp[i-1][0];
for(int j=1;j<column;j++) dp[0][j] = grid[0][j]+dp[0][j-1];
for(int i=1;i<row;i++){
for(int j=1;j<column;j++){
dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+ grid[i][j];
}
}
return dp[row-1][column-1];
}
}
70、二叉树最大深度(易-低)
class Solution {
public int maxDepth(TreeNode root) {
return dfs(root);
}
public int dfs(TreeNode node){
if(node==null) return 0;
return Math.max(dfs(node.left),dfs(node.right))+1;
}
}
71、用栈实现队列(易-低)
class MyQueue {
Deque<Integer> stack1;
Deque<Integer> stack2;
public MyQueue() {
stack1 = new ArrayDeque<>();
stack2 = new ArrayDeque<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}
72、最大正方形(中-高)
思路:i和j所指的坐标是正方形的右下角,通过i-1且j和i-1且j-1和i且j-1也就是左和左上和上这三个位置的最小值来决定是否可以组成正方形
重点1:通过这步来为i=0且j!=0和i!=0且j=0的第1行和第1列赋上初值
class Solution {
public int maximalSquare(char[][] matrix) {
//思路:i和j所指的坐标是正方形的右下角,通过i-1且j和i-1且j-1和i且j-1也就是左和左上和上这三个位置的最小值来决定是否可以组成正方形
int row = matrix.length;
int column = matrix[0].length;
int[][] dp = new int[row][column];
int maxValue = 0;
for(int i=0;i<row;i++)
for(int j=0;j<column;j++){
if(matrix[i][j]=='1'){
if(i==0 || j==0){ //重点:通过这步来为i=0且j!=0和i!=0且j=0的第1行和第1列赋上初值
dp[i][j]=1;
}else{
dp[i][j] = Math.min(dp[i-1][j],Math.min(dp[i-1][j-1],dp[i][j-1]))+1;
}
maxValue = Math.max(maxValue,dp[i][j]);
}
}
return maxValue*maxValue;
}
}
73、组合总和(中-中)
重点1:如果想要不出现3 5和5 3同时出现的情况,要保证每次取的元素是在当前元素的位置上或者后面,因此加入一个start标志
易错点1:不要写target -= candidates[i],直接写target-candidates[i]
class Solution {
Deque<Integer> deq = new ArrayDeque<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
dfs(candidates,target,0);
return res;
}
public void dfs(int[] candidates,int target,int start){
if(target==0){
res.add(new ArrayList<>(deq));
return;
}
if(target<0){
return;
}
for(int i=start;i<candidates.length;i++){ //重点1:如果想要不出现3 5和5 3同时出现的情况,要保证每次取的元素是在当前元素的位置上或者后面,因此加入一个start标志
deq.addLast(candidates[i]);
dfs(candidates,target - candidates[i],i); //易错点1:不要写target -= candidates[i],直接写target-candidates[i]
deq.removeLast();
}
}
}
74、路径总和(中-低)
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return dfs(root,targetSum);
}
public boolean dfs(TreeNode node,int targetSum){
if(node==null) return false;
targetSum -= node.val; //重点1:先对targetSum进行扣减
if(node.left==null && node.right==null && targetSum==0){ //重点2:targetSum才能为0,否则条件应该为targetSum==node.val
return true;
}
return dfs(node.left,targetSum) || dfs(node.right,targetSum);
}
}
75、最长公共前缀(低-低)
class Solution {
public String longestCommonPrefix(String[] strs) {
int n = strs.length;
int minLen = Integer.MAX_VALUE;
for(int i=0;i<n;i++){
minLen = Math.min(minLen,strs[i].length());
}
int len = 0;
for(int i=0;i<minLen;i++){
int x = strs[0].charAt(i);
for(int j=1;j<n;j++){
if(strs[j].charAt(i)!=x){
return strs[0].substring(0,len);
}
}
len++;
}
return strs[0].substring(0,len);
}
}
76、颜色分类(难-低)
思路:p0指向数组头,代表0元素。p1指向数组尾,代表2元素。再设置一个i从头遍历到p1的位置。p0和p1都会动态变化。 假如i遇到2元素就和p1换,i遇到0元素就和p0换,然后改变p0 p1 i的位置,逐渐让所有元素到达正确的位置。
重点1:要用while因为怕的是2 1 2这种情况,换完第1趟之后还是2 1 2最后的p1是左移了,但是i也++了,但是第1个2仍旧在错误的位置。所以需要用while保证i不动,然后让第1位确定不是2。
易错点1:while要加上i<=p1,不然的话会出现越界的情况。
class Solution {
//思路:p0指向数组头,代表0元素。p1指向数组尾,代表2元素。再设置一个i从头遍历到p1的位置。p0和p1都会动态变化。
public void sortColors(int[] nums) {
int n = nums.length;
int p0 = 0,p1 = n-1,i=0;
while(i<=p1){
while(i<=p1 && nums[i]==2){ //重点1:要用while因为怕的是2 1 2这种情况,换完第1趟之后还是2 1 2最后的p1是左移了,但是i也++了,但是第1个2仍旧在错误的位置。所以需要用while保证i不动,然后让第1位确定不是2。
//易错点1:while要加上i<=p1,不然的话会出现越界的情况
swap(nums,i,p1);
--p1;
}
if(nums[i]==0){
swap(nums,i,p0);
++p0;
}
i++;
}
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
77、寻找旋转排序数组中的最小值(难-低)
class Solution {
public int findMin(int[] nums) {
//思路:整个数组会被分为2个升序数组,比如:4 5 6 7和1 2 3。此时让最小值为第0位元素,代表一半数组的最小值。
int start = 0,end = nums.length-1,min=nums[0];
while(start<=end){
int mid = (start+end)/2;
if(nums[mid]<min){ //比如2会小于4,此时正确答案是在2的左侧,所以2的右侧可以收缩
min = nums[mid];
end = mid-1;
}else{ //nums[mid]>=min所以4 5 6 7 1 2 3比如6大于4所以6左边都可以不用看
end = mid-1;
}
}
return min;
}
}
78、寻找峰值(难-低)
class Solution {
public int findPeakElement(int[] nums) {
int n = nums.length;
int x = (int)(Math.random()*n);
while(true){
boolean isLeftLower = (x==0 || nums[x-1]<nums[x]) ;
boolean isRightLower = (x==n-1 || nums[x+1]<nums[x]);
if(isLeftLower && isRightLower){
return x;
}
if(!isLeftLower){ //左边比右边高 1 2 3 4 3 1,比如这里的4和3
x--;
}else{
x++;
}
}
}
}
79、用Rand7()实现Rand10() (难-低)
rand7()生成1~7,rand7()-1生成0~6,(rand7()-1)*7生成0~42,rand7()*7生成0~49,刨除掉大于40的数,剩下1~40出现是等概率的,然后%10会得到0~9的数,最后+1会得到1~10的数。
class Solution extends SolBase {
int ans = 0;
public int rand10() {
//rand7()生成1-7
//rand7()-1生成0-6
//(rand7()-1)*7生成0 7 14 21 28 35 42
//rand7()*7 生成7 14 21 28 35 42 49
do{
ans = (rand7()-1)*7+rand7();
}while(ans>40);
//只生成1到40的数
//(1-40)%10得到0到9,最后加上1就是1到9
return 1+ans%10;
}
}
80、乘积最大子数组(中-中)
class Solution {
public int maxProduct(int[] nums) {
int n = nums.length;
int[] dpMax = new int[n]; //记录最大值
int[] dpMin = new int[n]; //记录最小值
int maxValue = nums[0];
dpMax[0]=nums[0];
dpMin[0]=nums[0];
for(int i=1;i<n;i++){
dpMin[i] = Math.min(Math.min(dpMin[i-1]*nums[i],dpMax[i-1]*nums[i]),nums[i]);
dpMax[i] = Math.max(Math.max(dpMax[i-1]*nums[i],dpMin[i-1]*nums[i]),nums[i]);
maxValue = Math.max(maxValue,dpMax[i]);
}
return maxValue;
}
}
81、字符串解码(难-中)
class Solution {
public String decodeString(String s) {
int n = s.length();
Deque<Integer> numStack = new ArrayDeque<>();
Deque<String> strStack = new ArrayDeque<>();
int num = 0;
String str = "";
for(int i=0;i<n;i++){
char x = s.charAt(i);
if(Character.isDigit(x)){
num = num*10+(x-'0');
}else if(Character.isLetter(x)){
str += x;
}else if(x=='['){
numStack.push(num); //数字栈入栈
num = 0;
strStack.push(str); //字符栈入栈
str = "";
}else if(x==']'){
int cnt = numStack.pop(); //弹出栈顶的数字
String temp = str; //临时字符串temp来承接str的字符串
str = ""; //易错点1:str清空
for(int j=0;j<cnt;j++){ //重复生成cnt次
str += temp; //用str来存储最新的多次循环后的字符串
}
str = strStack.pop() + str; //易错点2:在str的前面拼接上栈顶的字符串元素
}
}
return str;
}
}
82、寻找两个正序数组中位数(难-高)
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
int left = (n+m+1)/2;
int right = (n+m+2)/2;
return (dfs(nums1,0,n-1,nums2,0,m-1,left)+dfs(nums1,0,n-1,nums2,0,m-1,right))*0.5;
}
//重点1:k的值代表的是从start开始的偏移量
public int dfs(int[] nums1,int start1,int end1,int[] nums2,int start2,int end2,int k){
int len1 = end1-start1+1; //易错点1:len的长度计算要记住
int len2 = end2-start2+1;
if(len1>len2) return dfs(nums2,start2,end2,nums1,start1,end1,k); //易错点1:保证len1比len2小,记得要return
if(len1==0) return nums2[start2+k-1]; //易错点2:len1长度为0了,直接从len2取,记得要-1
if(k==1) return Math.min(nums1[start1],nums2[start2]);
int i = start1 + Math.min(k/2,len1)-1; //重点2:要取k/2和len1的最小值,避免某一个数组的长度很短,短于k/2
int j = start2 + Math.min(k/2,len2)-1;
if(nums1[i]>nums2[j]){ //重点3:如果nums1[i]和nums2[j]其中任意一方更小,就应该排除掉更小一方的前面所有数,因此是i+1或者j+1
return dfs(nums1,start1,end1,nums2,j+1,end2,k-(j-start2+1)); //易错点3:k-(j-start2+1)要+1,而且要理解j-start2
}else{
return dfs(nums1,i+1,end1,nums2,start2,end2,k-(i-start1+1));
}
}
}