A 例子逻辑(核心难点):数学推导的方式获取规律,推理。
例子是整个代码的前提,先用例子找逻辑规律,找操作的规律才是核心,如dp和dp-1。找到后才能进行编码,用多个例子完善代码逻辑。 把例子转化成代码表示。找到状态转移方程也就是逻辑规律。先找到题的规律约束。
自己积累例子逻辑图,必须自己写例子推导。
B 循环(实现,简单):
循环用法:以循环为单位来分析。
循环用法:起止逻辑更新四步。
循环对象:是容器(树,数组,list,n)。
循环核心:是逻辑。循环要找到要被循环的对象才行。
循环表示:for、while、递归代表循环,递归的整个函数就是循环。
1定义(起):容器[指针],变量定义赋值。面向对象,理解代码的物理含义。
2.止if:if有return的就是结束条件,结束条件可能有好多种,结束条件的位置在循环首部。
3. 分类和分步:逻辑(操作) 。
A分类:if elseif else代表分类,if并列分类可以针对一个对象,也可以针对互补的情况。
B分步: if嵌套或者if if 并列代表分步,或者按照先后顺序。
优化逻辑:就是去重复计算,空间换时间。
4.更新(指针)。 递归也是对指针变量更新。
3
set
set类+repeat 变量含义
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> set = new HashSet<Integer>();//Integer不是inteager
int repeat = -1;
for (int num : nums) {
if (!set.add(num)) {//重复
repeat = num;
break;
}
}
return repeat;
}
}
数组索引和值,多种情况
class Solution {
public int findRepeatNumber(int[] nums) {
int i = 0;
while(i < nums.length) {
if(nums[i] == i) {等于就跳过
i++;
continue;
}
if(nums[nums[i]] == nums[i]) return nums[i];//核心判断,找到了重复值
int tmp = nums[i];//交换位置,i不增加继续用替换后的数判断是否重复了。
nums[i] = nums[tmp];
nums[tmp] = tmp;
}
return -1;
}
}
二维数组双指针操作+三种情况大于小于等于+判空情况
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {¥
return false;
}
int rows = matrix.length, columns = matrix[0].length;
int row = 0, column = columns - 1;//双指针
while (row < rows && column >= 0) {//必须>= 0,循环结束条件和指针有关系
int num = matrix[row][column];
if (num == target) {
return true;
} else if (num > target) {
column--;
} else {
row++;
}
}
return false;
}
}
class Solution {
public String replaceSpace(String s) {
int length = s.length();
char[] array = new char[length * 3];
int size = 0;
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == ' ') {
array[size++] = '%';
array[size++] = '2';
array[size++] = '0';
} else {
array[size++] = c;
}
}
String newStr = new String(array, 0, size);
return newStr;
}
}
String类遍历
class Solution {
public String replaceSpace(String s) {
StringBuilder builder = new StringBuilder();
for(int i = 0;i<s.length();i++){//遍历
if(s.charAt(i)==' ') builder.append("%20");//1.空格' '不是双引号append不是appead
else builder.append(s.charAt(i));//2.非空格charAt
}
return builder.toString();
}
}
栈和链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode temp = head;
while (temp != null) {//1.栈
stack.push(temp);
temp = temp.next;
}
int size = stack.size();
int[] print = new int[size];
for (int i = 0; i < size; i++) {//2.遍历栈
print[i] = stack.pop().val;
}
return print;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
递归循环法+list存结果+数组存结果:必备
class Solution {
ArrayList<Integer> tmp = new ArrayList<Integer>();//临时保存,最后要返回给数组的
public int[] reversePrint(ListNode head) {
recur(head);//调用递归
int[] res = new int[tmp.size()];
for(int i = 0; i < res.length; i++)
res[i] = tmp.get(i);
return res;结果用数组存起来
}
//递归三要素,递归也是一种遍历
void recur(ListNode head) {
if(head == null) return;//结束条件
recur(head.next);//循环条件,递归就是循环for的作用,循环控制语句
tmp.add(head.val);//1操作:每次递归遍历的处理核心,循环体,在递归前面就是顺序处理,在递归后面就是倒序处理
}
}
有返回值+前后操作递归(分别构建他的左右子树,问题分解,最后考虑边界(空为边界))+双指针
class Solution {
int[] preorder;//放成员里
HashMap<Integer, Integer> dic = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
for(int i = 0; i < inorder.length; i++)//1.创建map存位置,好找
dic.put(inorder[i], i);
return recur(0, 0, inorder.length - 1);
}
TreeNode recur(int root, int left, int right) {
if(left > right) return null; // 递归终止结束条件
TreeNode node = new TreeNode(preorder[root]); // 1.建立根节点
int i = dic.get(preorder[root]); // 2.划分根节点、左子树、右子树
node.left = recur(root + 1, left, i - 1); // 开启左子树递归循环下一次
node.right = recur(root + 1 + i - left , i + 1, right); // 开启右子树递归循环下一次,root + i - left + 1难点 i - left 是中间的距离
return node; // 3.回溯返回根节点
}
}
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int[] preorder; int[] inorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder; this.inorder = inorder;
return recur(0, 0, inorder.length - 1); //初始
}
TreeNode recur(int root, int left, int right) {
if(left>right)return null;
TreeNode node = new TreeNode(preorder[root]);
int i =0;
while(preorder[root]!=inorder[i])i++;
node.left=recur(root+1,left,i-1);
node.right=recur(root + i - left + 1,i+1,right);
return node;
}
}
自己写的
class Solution {
int[] preorder; int[] inorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder; this.inorder = inorder;
if(preorder.length==0)return null;
return recur(0, 0, inorder.length - 1); //初始
}
TreeNode recur(int root, int left, int right) {
if(left>right)return null; //结束情况
if(left==right)return new TreeNode(preorder[root]); //结束情况
TreeNode node = new TreeNode(preorder[root]);
int i =0;
while(preorder[root]!=inorder[i])i++;
node.left=recur(root+1,left,i-1);
node.right=recur(root + i - left + 1,i+1,right);
return node;
}
}
边界i情况改写
class Solution {
int[] preorder; int[] inorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder; this.inorder = inorder;
if(preorder.length==0)return null;
return recur(0, 0, inorder.length - 1); //初始
}
TreeNode recur(int root, int left, int right) {
// if(left>right)return null;
if(left==right)return new TreeNode(preorder[root]);
TreeNode node = new TreeNode(preorder[root]);
int i =0;
while(preorder[root]!=inorder[i])i++;
if(left<=i-1){
node.left=recur(root+1,left,i-1);}
if(right>=i+1){
node.right=recur(root + i - left + 1,i+1,right);
}
return node;
}
}
删掉多余代码
class Solution {
int[] preorder; int[] inorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder; this.inorder = inorder;
if(preorder.length==0)return null;
return recur(0, 0, inorder.length - 1); //初始
}
TreeNode recur(int root, int left, int right) {
// if(left>right)return null;
//if(left==right)return new TreeNode(preorder[root]); 不需要==的情况了,操作直接把最后一次的情况给处理了
TreeNode node = new TreeNode(preorder[root]);
int i =0;
while(preorder[root]!=inorder[i])i++;
if(left<=i-1){
node.left=recur(root+1,left,i-1);}
if(right>=i+1){
node.right=recur(root + i - left + 1,i+1,right);
}
return node;
}
}
9.
Deque当作栈操作+删除的三种情况
class CQueue {
Deque<Integer> stack1;
Deque<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<Integer>();
stack2 = new LinkedList<Integer>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
// 1.如果第二个栈为空
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {//empty
stack2.push(stack1.pop());
}
}
if (stack2.isEmpty()) {//2.还为空就是没数了¥¥¥边界
return -1;
} else {
int deleteItem = stack2.pop();//3.正确的时候
return deleteItem;
}
}
}
class CQueue {
Deque<Integer> stack1;
Deque<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<Integer>();
stack2 = new LinkedList<Integer>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
//非嵌套if,也不是并列if,只是分步的if
if(stack2.isEmpty())return -1;
else return stack2.pop();
}
}
10
动态规划:一个for
class Solution {
public int fib(int n) {
final int MOD = 1000000007;
if (n < 2) {//意外情况
return n;
}
int p = 0, q = 0, r = 1;
for (int i = 2; i <= n; ++i) {
p = q;
q = r;
r = (p + q) % MOD;¥¥模大数,不让r溢出来
}
return r;
}
}
class Solution {
public int fib(int n) {
if(n==0)return 0;
int p = 0, q = 1, r = 1;final int MOD = 1000000007;
for (int i = 2; i <= n; ++i) {
r = (p + q) % MOD;
p=q;
q=r;
}
return r;
}
}
递归分析+动态规划
class Solution {
public int numWays(int n) {
if (n <= 1) return 1;
int a = 1, b = 1, c = 0;//从0开始的
for (int i = 2; i <= n; i++){
c = (a + b) % 1000000007;
a = b;
b = c;
}
return c % 1000000007;
}
}
????
//二分查找操作(有序)+数组双指针+停止条件
class Solution {
public int minArray(int[] numbers) {
int low = 0;
int high = numbers.length - 1;
while (low < high) {//停止条件+循环条件:两个指针的大小比较
int pivot = low + (high - low) / 2;//1.取中值
if (numbers[pivot] < numbers[high]) {//2.比较大小 先比较右边不能重复
high = pivot;
} else if (numbers[pivot] > numbers[high]) {
low = pivot + 1;
} else {//等于的情况
high -= 1;
}
}
return numbers[low];
}
}
二维数组双指针+回溯法(都遍历一次)+前后操作递归(三种情况if语句,成功失败和中间(没用if))
class Solution {
public boolean exist(char[][] board, String word) {
char[] s = word.toCharArray();
for(int i = 0;i < board.length;i++){
for(int j = 0;j < board[0].length;j++){//随意一个进行递归
if(dfs(board,s,i,j,0)){//boolean类型返回
return true;
}
}
}
return false;
}
//迭代,整个函数就是循环,if有return的就是结束条件
public boolean dfs(char[][] board,char[] word,int i,int j,int index){
//边界
//1.字母索引越界或该字母board[i][j]不是对应的word[index],则返回false
if(i > board.length-1 || i < 0 || j > board[0].length-1 || j < 0 || board[i][j] != word[index]){
return false;
}
//2.如果已经访问到单词的最后一个字母,则返回true,正确结束条件放在最前面,必不可少。
if(index == word.length - 1){
return true;
}
char temp = board[i][j];
//3.修改当前值,代表已经访问过,在后面的递归中存在了
board[i][j] = '#';
//递归换位置再代入,二维数组就有两个指针。
boolean res = dfs(board,word,i+1,j,index+1) || dfs(board,word,i,j+1,index+1) ||
dfs(board,word,i-1,j,index+1) || dfs(board,word,i,j-1,index+1);//用或 res暂存
//4.复原
board[i][j] = temp;
return res;
}
}
对于每个节点都有n种情况的必须用递归。暴力穷举升级版.
board复原是为了防止重复进去,重复进去也算路径。注意可以重复进入的题。
回溯递归就是全部都找一次,判断每次有没有找过
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
//从[i,j]这个坐标开始查找,随即一个个查找
if (dfs(board, words, i, j, 0))
return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int index) {
//1.边界的判断,如果越界直接返回false。index表示的是查找到字符串word的第几个字符,
//2.如果这个字符不等于board[i][j],说明验证这个坐标路径是走不通的,直接返回false
if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[index])
return false;
//3.如果word的每个字符都查找完了,直接返回true
if (index == word.length - 1)
return true;
//4.把当前坐标的值保存下来,为了在最后复原
char tmp = board[i][j];
//5.然后修改当前坐标的值
board[i][j] = '.';
//走递归,沿着当前坐标的上下左右4个方向查找
boolean res = dfs(board, word, i + 1, j, index + 1) || dfs(board, word, i - 1, j, index + 1) ||
dfs(board, word, i, j + 1, index + 1) || dfs(board, word, i, j - 1, index + 1);
//6.递归之后再把当前的坐标复原
board[i][j] = tmp;
return res;
}
队列的添加和遍历(广度优先)
class Solution {
public int movingCount(int m, int n, int k) {
if (k == 0) {
return 1;
}
Queue<int[]> queue = new LinkedList<int[]>();
// 向右和向下的方向数组
int[] dx = {0, 1};
int[] dy = {1, 0};
boolean[][] vis = new boolean[m][n];//存结果的二维数组
queue.offer(new int[]{0, 0});
vis[0][0] = true;
int ans = 1;
while (!queue.isEmpty()) {
int[] cell = queue.poll();//取出
int x = cell[0], y = cell[1];
for (int i = 0; i < 2; ++i) {
int tx = dx[i] + x;
int ty = dy[i] + y;//增加,更换位置操作
if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) {//判断是否大于K
continue;
}
queue.offer(new int[]{tx, ty});//不大于则加入队列
vis[tx][ty] = true;
ans++;
}
}
return ans;
}
private int get(int x) {//拆分方法
int res = 0;
while (x != 0) {
res += x % 10;
x /= 10;
}
return res;
}
}
前后操作递归法(即深度优先)+索引操作
class Solution {
public int movingCount(int m, int n, int k) {¥¥没数组,只操作下标
// 设置标记数组,标记元素是否访问过#¥
boolean[][] labels = new boolean[m][n];
// 图的dfs,起始点只有一个,因此只要从原点进行DFS即可¥¥
return dfs(m,n,k,0,0,labels);
}
private int dfs(int m, int n, int k, int x, int y, boolean[][] labels){
// 1.如果下标越界,该当前位置元素已经被访问过
//2.或者数位和大于k,则返回 0,结束递归
if(x < 0 || y < 0 || x >= m || y >= n || labels[x][y] || x/10+x%10+y/10+y%10 > k)//或者合并返回0
return 0;
// 3.如果该位置元素可达,就将标记位设置为 true
labels[x][y] = true;//判断是否来过
// 继续统计各个方向的可访问位置/* 再次进入下一层递归
* 这里只遍历了右、下,左、上不需要遍历,
* 因为是从左上开始的,到右下结束,所以当前节点都是从左上来的
*/
//4.返回相加
return 1 + dfs(m,n,k,x+1,y,labels) + dfs(m,n,k,x,y+1,labels);//向右和下递归,循环体
}
}
class Solution {
public int movingCount(int m, int n, int k) {
if (k == 0) {
return 1;
}
boolean[][] labels = new boolean[m][n];
return dfs(m,n,k,0,0,labels);
}
private int dfs(int m, int n, int k, int x, int y, boolean[][] labels){
if(x < 0 || y < 0 || x >= m || y >= n || labels[x][y] || x/10+x%10+y/10+y%10 > k)//或者合并返回0
return 0;
labels[x][y] = true;//判断是否来过
return 1+dfs(m,n,k,x+1,y,labels)+dfs(m,n,k,x,y+1,labels);
}
}
class Solution {
public int cuttingRope(int n) {
if (n < 2) {
return n;
}
if(n==2)return 1;
if(n==3)return 2;
if(n==4)return 4;
int[] dp = new int[n +1];
dp[1] = 1;//为了防止
dp[2] = 1;
dp[3] = 2;
dp[4] = 4;
for(int i = 5; i < n + 1; i++){
for(int j=1;j<i;j++){
dp[i]=Math.max(dp[i],Math.max(j, dp[j]) * Math.max(i-j, dp[i - j]));
}
}
return dp[n];//返回最后一个
}
}
class Solution {
public int cuttingRope(int n) {
if(n == 2)return 1;
if(n == 3)return 2;
if(n == 4)return 4;
int res = 1;
while(n > 4){
res *= 3;//找规律,代码最简单
n -= 3;
}
return res*n;
}
}
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int m=1,count=0;
for(int j=0;j<32;j++){
if((m&n)!=0)count++;
m=m<<1;
}
return count;
}
}
超时:
class Solution {
public double myPow(double x, int n) {
if(x==0)return 0;
if(n!=0) {
double res=1;
int m=0;
if(n<0)m=-n;
else m=n;
for(int i=0;i<m;i++){
res*=x;
}
if(n>0)return res;
else return 1/res;
}
if(n==0){
return 1;
}
return 0;
}
}
对n分情况判断
class Solution {
double res = 1;
public double myPow(double x, int n) {
while(n > 0){
//每次循环就判断n是否是偶数
if(n%2==0) {
//如果是偶数那么原数x先平方,然后n的值除以2
x *= x;
n=n/2;
}
res *= x;
n--;
}
//同理
while(n < 0){
if(n%2==0) {
x*= x;
n=n/2;
}
res*= 1/x;
n++;
}
return res;
}
}
class Solution {
public int[] printNumbers(int n) {
int end=(int)Math.pow(10,n)-1;
int[] res = new int[end];
for(int i = 0; i < end; i++)
res[i]=i+1;//debug查看数字,从0开始的
return res;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode p=head;ListNode pre=head;
if(head.val==val){
head=head.next;
return head;
}
while(p!=null){
if(p.val==val){
pre.next=p.next;
return head;
}
pre=p;
p=p.next;
}
return null;
}
}
共有点e数字符号四个对象四种情况:模仿从头到尾遍历的过程:带入一个例子
分解变量情况(分情况):根据不同对象(切入点)分出不同的情况、多指针变量的利用
class Solution {
public boolean isNumber(String s) {
if(s == null || s.length() == 0){//结束
return false;
}
//定义指针
boolean numSeen = false;
boolean dotSeen = false;
boolean eSeen = false;
char[] str = s.trim().toCharArray();
for(int i = 0;i < str.length; i++){
if(str[i] >= '0' && str[i] <= '9'){ //1.分情况:数
numSeen = true;
}else if(str[i] == '.'){//2.点
//点.之前不能出现.或者e
if(dotSeen || eSeen){
return false;
}
dotSeen = true;
}else if(str[i] == 'e' || str[i] == 'E'){//3.e
//e之前不能出现e,必须出现数
if(eSeen || !numSeen){
return false;
}
eSeen = true;
numSeen = false;//重置numSeen,排除123e或者123e+的情况,确保e之后也出现数,bug
}else if(str[i] == '-' || str[i] == '+'){//4符好
//+-出现在0位置或者e/E的后面第一个位置才是合法的
if(i != 0 && str[i-1] != 'e' && str[i-1] != 'E'){$$bug 是与的关系。都符合才是false
//if( i==0||str[i-1]=='E'||str[i-1]=='e')return true;是错误的,比如+++,范围太大了或,符合一个就成功了
return false;
}
}else{//5.其他不合法字符
return false;
}
}
return numSeen;¥¥最后一个数字的情况,肯定是数字,前面也都没返回false。
}
}
数组双指针变量法
class Solution {
public int[] exchange(int[] nums) {
int i=0,j=nums.length-1,tmp;//第一步设置索引和变量
while(i<j){//结束+循环条件
//首先使i在偶j在奇数上才能交换
while(i<j&&(nums[i]&1)==1)i++;//1.判断奇偶¥¥i<j的条件
while(i<j&&(nums[j]&1)==0)j--;//2.偶
tmp=nums[i];//3.交换奇偶
nums[i]=nums[j];
nums[j]=tmp;
}
return nums;
}
}
遍历链表,第一次遍历是为了计算多少个
单纯遍历两次,计数
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
int n = 0;
ListNode node = null;
//1.算n
for (node = head; node != null; node = node.next) {//遍历链表,开始、结束、循环体,变量更新
n++;
}
2.倒数
for (node = head; n > k; n--) {
node = node.next;
}
//3.返回
return node;
}
}
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
int n = 0;
ListNode node = null;
//1.算n
for (node = head; node != null; node = node.next) {//遍历链表,开始、结束、循环体,变量更新
n++;
}
ListNode nodes = head;
for(int j=0;j<n-k;j++){//正向思维,算第几个。画图算。
nodes=nodes.next;
}
return nodes;
}
}
画图写过程,指针一定要
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head, pre = null;
while(cur != null) {
ListNode tmp = cur.next; // 暂存后继节点 cur.next
cur.next = pre; // 修改 next 引用指向
pre = cur; // pre 暂存 cur
cur = tmp; // cur 访问下一节点
}
return pre;
}
}
自己写的
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null)return null;
ListNode node=head;
return recur(node,null);
}
private ListNode recur(ListNode cur, ListNode pre) {
if(cur==null)return pre;
ListNode t=cur.next;
cur.next=pre;//赋值
ListNode res=recur(t,cur);
return res;
}
}
class Solution {
public ListNode reverseList(ListNode head) {
return recur(head, null); // 调用递归并返回
}
private ListNode recur(ListNode cur, ListNode pre) {
if (cur == null) return pre; // 终止条件
ListNode res = recur(cur.next, cur); // 递归后继节点
cur.next = pre; // 修改节点引用指向
return res; // 返回反转链表的头节点
}
}
25.合并列表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy=new ListNode();
ListNode p=dummy;
ListNode p1=l1;
ListNode p2=l2;
while(p1!=null&&p2!=null){
if(p1.val>p2.val){
p.next=p2;
p2=p2.next;
}else{
p.next=p1;
p1=p1.next;
}
p=p.next;
}
if(p1==null){
p.next=p2;
}
if(p2==null){
p.next=p1;
}
return dummy.next;
}
}
双重前后操作递归(递归是当前这一轮也是最后一轮,两种情况带入完善好代码)+判空为结束条件,有对象必然判空
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {//作用是挨个比较每个节点
//结束条件
//递归条件
//1.结果并列起来
return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));前面是判断异常情况,后面是三种递归属于并列的情况,一个true即可。
}
boolean recur(TreeNode A, TreeNode B) {//作用是比较AB是否相同
if(B == null) return true;//结束:B结束才为正确结局$
if(A == null || A.val != B.val) return false;//结束2:判断a为空¥¥必然判空
return recur(A.left, B.left) && recur(A.right, B.right);//递归条件+1.与操作
}
}
自己写的
递归看成一次遍历的操作,递归看成他的子树
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null||B==null)return false;//意外情况
return recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
boolean recur(TreeNode A, TreeNode B) {//作用是比较AB是否相同
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return recur( A.left, B.left)&& recur( A.right, B.right);
}
}
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null||B==null)return false;//非空
boolean f=false;
if(A.val==B.val) f=recur(A,B);//边界
return f|| isSubStructure(A.left,B)||isSubStructure(A.right,B); //操作
}
boolean recur(TreeNode A, TreeNode B) {//作用是比较AB是否相同
if(B == null) return true;//边界
if(A == null || A.val != B.val) return false;//边界
return recur( A.left, B.left)&& recur( A.right, B.right);//操作
}
}
有返回的后操作递归:先递归到头,在交换,再从尾巴到根(搞清楚递归是怎么跑的)
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {//结束
return null;
}
TreeNode left = mirrorTree(root.left);//递归
TreeNode right = mirrorTree(root.right);
root.left = right;//1.操作:左子树,返回的数据##直接赋值
root.right = left;//2.右
return root;//3.返回递归结果
}
}
自己写的,也可以先交换,再更新
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root==null )return null;
TreeNode tmp= root.left;
root.left=root.right;
root.right=tmp;
TreeNode left=mirrorTree(root.left);
TreeNode right=mirrorTree(root.right);
return root;
}
}
定义
递归
class Solution {
public boolean isSymmetric(TreeNode root) {
//结束
//递归
return root == null ? true : recur(root.left, root.right);//调用递归和判断异常情况#空为正确
}
boolean recur(TreeNode L, TreeNode R) {¥需要这两个索引
if(L == null && R == null) return true;//正确结束
if(L == null || R == null || L.val != R.val) return false;//错误结束
return recur(L.left, R.right) && recur(L.right, R.left);//进入下一次循环,1.操作:两个循环是与的关系
}
}
错误代码
public boolean isSymmetric(TreeNode root) {
if(root==null)return true;
if(root.left==null&&root.right==null)return true;
if(root.left==null&&root.right!=null)return false;
if(root.right==null&&root.left!=null)return false;
if(root.right.val!= root.left.val )return false;//不是只是他的左右子树
return isSymmetric(root.left)&&isSymmetric(root.right);
}
二维数组多指针
定义好边界上下左右、,行列为指针+指针的维护。有指针必维护
class Solution {
public int[] spiralOrder(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {//结束
return new int[0];
}
int rows = matrix.length, columns = matrix[0].length;
int[] order = new int[rows * columns];
int index = 0;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;//指针比较多,定义上下左右变量
while (left <= right && top <= bottom) {//结束条件 +循环
for (int column = left; column <= right; column++) {//1.
order[index++] = matrix[top][column];
}
for (int row = top + 1; row <= bottom; row++) {//2.
order[index++] = matrix[row][right];
}
if (left < right && top < bottom) {//结束条件
for (int column = right - 1; column > left; column--) {¥3.从右-1开始的,模拟出每一步细节再写代码
order[index++] = matrix[bottom][column];
}
for (int row = bottom; row > top; row--) {//4.
order[index++] = matrix[row][left];
}
}
//5.指针更新
left++;
right--;
top++;
bottom--;
}
return order;//6返回
}
}
数组遍历,分解出步奏
栈的操作过程(边加边弹出)和物理意义,遍历数组
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int i = 0;
for(int num : pushed) {// 结束条件
stack.push(num); // 1. 先都num 入栈
//结束条件 判断非空
while(!stack.isEmpty() && stack.peek() == popped[i]) { //2 相等就全出栈
stack.pop();
i++;//3.维护popped的指针
}
}
return stack.isEmpty();//4.返回 判断依据¥¥
}
}
队列遍历(广度优先)
class Solution {
public int[] levelOrder(TreeNode root) {
if(root==null) return new int[0];
Queue<TreeNode> queue=new LinkedList<>(){{add(root);}};¥linklist实现的queen
ArrayList<Integer> ans=new ArrayList<>();//集合没指针
while(!queue.isEmpty()){//结束
TreeNode node=queue.poll();//¥1.取出来
ans.add(node.val);//¥2.存结果
if(node.left!=null)queue.add(node.left);//¥3.存结果
if(node.right!=null)queue.add(node.right);//¥4.存结果
}
int [] res=new int[ans.size()];
for(int i=0;i<ans.size();i++){//5
res[i]=ans.get(i);
}
}
}
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue=new LinkedList<>();
List<List<Integer>> res=new ArrayList<>();//一行一个list。存结果
if(root!=null) queue.add(root);//结束+1存
while(!queue.isEmpty()){//结束
List<Integer> tmp=new ArrayList<>();//1.临时结果,辅助队列定义
for(int i=queue.size();i>0;i--){//结束
TreeNode node=queue.poll();//2.取出来
tmp.add(node.val);//3.存
if(node.left!=null)queue.add(node.left);
if(node.right!=null)queue.add(node.right);
}
res.add(tmp);//4.存本次的
}
return res;
}
}
举一反三就是增加了特殊情况if语句
广度遍历利用队列,特殊情况+辅助队列+辅助链表添加头尾
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue=new LinkedList<>();
List<List<Integer>> res=new ArrayList<>();
if(root!=null)queue.add(root);//结束
while(!queue.isEmpty()){//结束
LinkedList<Integer> tmp=new LinkedList<>();//1.辅助
for(int i=queue.size();i>0;i--){
TreeNode node=queue.poll();//2.取出来
if(res.size()%2==0)tmp.addLast(node.val);//3. 奇数行判断 qubi而在这里的if语句,也是核心
else tmp.addFirst(node.val);//4. 偶数行判断
if(node.left!=null) queue.add(node.left);//5存.
if(node.right!=null)queue.add(node.right);//6存.
}
res.add(tmp);//7存. 这里它的size才加一
}
return res;//8返回
}
}
变量的意义
前后操作递归
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder,0,postorder.length-1);//调用递归,参数为后序数组和范围ij
}
boolean recur(int[] postorder,int i,int j){%返回布尔值
if(i>=j)return true;//结束,终止条件范围为0,i=j。
int p=i;//定义指针索引¥¥
while(postorder[p]<postorder[j])p++;//1.找左子树的界
int m=p;//2.代表中点
while(postorder[p]> postorder[j])p++;//3.找右子树的界,看是否到了根节点那里,目的是判断是否是后序序列
//4.判断是否到了
//递归
//5.与操作和返回
return p==j&&recur(postorder,i,m-1)&&recur(postorder,m,j-1);//递归左右子树,返回boolean值
}
}
回溯法+递归前后都有操作:前面的操作(一般是出口+操作代码)一直执行到尾了在执行递归代码后面的操作。而循环是从头执行到尾,比较好理解。
class Solution {
LinkedList<List<Integer>> res = new LinkedList<>();//定义 存结果,维护结果list的list
LinkedList<Integer> path = new LinkedList<>(); //定义 路径实体,进行增删改查的维护
public List<List<Integer>> pathSum(TreeNode root, int sum) {
recur(root, sum);//递归代入标准参数
return res;
}
void recur(TreeNode root, int tar) {
if(root == null) return;//结束1:啥也不返回啥也不操作
path.add(root.val);//1.添加路劲¥¥都要加
tar -= root.val;//2.算目标
if(tar == 0 && root.left == null && root.right == null)//结束2:在递归前面正序执行,正确的结束条件¥必须是叶子节点
res.add(new LinkedList(path));//3.保存当前路径,不需要手动重置path或者tar,因为path在最后都手动删除了最后一个,而tar是迭代变量,会自动随着迭代恢复原来的值¥¥
recur(root.left, tar);//没结束继续递归
recur(root.right, tar);
path.removeLast();//4.维护了path,倒序执行压栈,把路径撤销,sum不用管,上次方法中没变。递归之后恢复
}
}
共同点:
都是向res这个ArrayList中填加了一个名为path的链表
不同点:
res.add(new ArrayList(path)):开辟一个独立地址,地址中存放的内容为path链表,后续path的变化不会影响到res,相遇先new再把path赋值给new
res.add(path):将res尾部指向了path地址,后续path内容的变化会导致res的变化。
哈希映射map的使用 +链表的使用
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;结束¥¥
Node cur = head;
Map<Node, Node> map = new HashMap<>();//定义 维护一个map表
// 1. 遍历复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != null) {
map.put(cur, new Node(cur.val));//一对映射,新链表的节点相当于都是散的。
cur = cur.next;
}
cur = head;//定义头节点
// 2. 构建新链表的 next 和 random 指向
while(cur != null) {
//给新节点设置next,只能是给新节点连接
map.get(cur).next = map.get(cur.next);//没办法同时给这两个属性赋值,因为链表得遍历,不能直接找到。
//给新节点设置random ,只能是给新节点连接
map.get(cur).random = map.get(cur.random);//给新节点的属性赋值
cur = cur.next;
}
// 3.. 返回新链表的头节点
return map.get(head);
}
}
纯链表操作
class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
// 1. 复制各节点,并构建拼接链表
while(cur != null) {
Node tmp = new Node(cur.val);
tmp.next = cur.next;
cur.next = tmp;
cur = tmp.next;
}
// 2. 构建各新节点的 random 指向
cur = head;
while(cur != null) {
if(cur.random != null)¥判空
cur.next.random = cur.random.next;
cur = cur.next.next;
}
// 3. 拆分两链表
cur = head.next;
Node pre = head, res = head.next;//把引用赋值给了两个指针。
while(cur.next != null) {
pre.next = pre.next.next;
cur.next = cur.next.next;
pre = pre.next;
cur = cur.next;
}
pre.next = null; // 单独处理原链表尾节点%
return res; // 返回新链表头节点%%只返回头节点就行
}
}
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
Node head; //定义头结点: 即第一个节点
Node pre; //定义全局 当前遍历节点的前导节点(或者说上一个节点),维护上一个节点¥成员变量
中序遍历递归(中间操作)+前导节点的维护+数据结构操作
public Node treeToDoublyList(Node root) {
//dfs将前后节点连接
dfs(root);
//结束 入参不是空节点条件下, 将尾节点和头结点连接
if (head != null){
head.left = pre;
pre.right = head;
}
return head;
}
private void dfs(Node root) {
if (root == null){//结束
return;
}
//1.记录右子树, 因为在递归过程中会修改掉root的右子树(因为pre可能就是root),中间修改了右子树的指向,左子树因为迭代的早,不会受到修改指向的影响
Node right = root.right;%%%
dfs(root.left);//循环
//2中序遍历的第一个节点, 记录为头结点
if (head == null){//¥¥¥用成员变量保存好第一个节点,为了和最后一个链接
head = root;
} else {
//3.双向链表互相连接
root.left = pre;//pre存放了前导节点¥¥left不是next
pre.right = root;
}
//4.更新维护前导节点
pre = root;
dfs(right);//循环到下一次
}
}
队列+字符串StringBuilder
public class Codec {
public String serialize(TreeNode root) {
if(root == null) return "[]";
StringBuilder res = new StringBuilder("[");//面向对象,有几个对象,对对象操作。
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
while(!queue.isEmpty()) {
TreeNode node = queue.poll();//1.取出
if(node != null) {//结束条件无处不在
res.append(node.val + ",");//2.字符串拼接
queue.add(node.left);//3.存
queue.add(node.right);
}
else res.append("null,");//4.
}
res.deleteCharAt(res.length() - 1);//5多余的 去掉逗号
res.append("]");//6.
return res.toString();//7.转化
}
队列(用队列一个个存树的节点再拿出来遍历,也可以递归法)+字符串
public TreeNode deserialize(String data) {
if(data.equals("[]")) return null;//结束
String[] vals = data.substring(1, data.length() - 1).split(",");//1.分割
TreeNode root = new TreeNode(Integer.parseInt(vals[0]));//1转化
Queue<TreeNode> queue = new LinkedList<>() {{ add(root); }};
int i = 1;
while(!queue.isEmpty()) {//结束
//循环内都是一样的操作,构造左右子树
TreeNode node = queue.poll();//1.取出
if(!vals[i].equals("null")) {//结束
node.left = new TreeNode(Integer.parseInt(vals[i]));//2.左子树
queue.add(node.left);//3.存
}
i++;//4.维护
if(!vals[i].equals("null")) {//结束
node.right = new TreeNode(Integer.parseInt(vals[i]));//5.右子树
queue.add(node.right);//6存
}
i++;//7.维护
}
return root;//8¥头节点不参与运算
}
}
Hashset去重复+前递归思路里有双重for+List存String
多层循环控制、多步,具体业务逻辑
class Solution {
private List<String> result;//存结果的容器
public String[] permutation(String s) {
//假设字符串长度为n(n>1),把字符串看成两个部分:第一个字符和后(n-1)个字符。当第一个字符是s中的某个字符时,后面的字符就是剩余的n-1个字符排列,总共的排列方式=n*(n-1个字符的排列),因此用递归来解决
result=new ArrayList<String>();
if(s==null||s.length()==0) return new String[0];//结束¥
if(s.length()==1){##结束
result.add(s);
return result.toArray(new String[result.size()]);//list转化为数组,字符串数组去接
}
result.add(s);
allSort(0);//调用
return result.toArray(new String[result.size()]);¥¥转成String数组
}
public void allSort(int index){
if(index>=result.get(0).length()-1) return;
int size=result.size();
int len=result.get(0).length();
for(int i=0;i<size;i++){//对result遍历
Set<Character> c_set=new HashSet<>();//定义:防止重复的容器set,每次都新建¥¥这里新建,每轮一个
c_set.add(result.get(i).charAt(index));//1.一样的字符不再交换
for(int j=index+1;j<len;j++){//2.依次和后面比较for循环,不重复则交换,index+1是后面的,index是当前位置
j索引代表的意义
char[] temp=result.get(i).toCharArray();//3.$$存放临时结果,改变不影响原来的结果,赋值了。这只是一个引用,虚参,实参赋值给他的。
if(!c_set.contains(temp[j])){//4.依次对比第一个空和后面的字母是否重复,重复交换结果一样没意义,判断重复操作
c_set.add(temp[j]);//5.添加不重复的
char c=temp[index];//6.交换操作
temp[index]=temp[j];
temp[j]=c;
String s=new String(temp,0,temp.length);//7.转化成String对象,String s=new String(temp);//也行¥¥复制字符串
result.add(s);//8.添加进去
}
}
}
allSort(++index);//递归到index+1,注意位置在for的后面。
}
}
自己实现,递归改循环
单次快排+数组多指针
数组+双指针+前操作递归=快排查找(前操作递归是经常用的--正序好理解,后操作递归是树,后递归比较难理解,递归比循环的优势:可以自由控制变量范围灵活多变,但是要确定好结束条件;同时递归也是一种分解思想)
class Solution {
public int majorityElement(int[] nums) {
int n = nums.length;
return qSort(nums,0,n-1,n/2);//递归变量,增加了个中位数,就是目的:找中位数,不用管奇偶,中间肯定过半了
}
public int qSort(int[] nums,int left,int right,int mid) {
int i=left,j=right;
while(i<j) {//确定最左边第一个值的位置
while(i<j && nums[j]>=nums[left])--j;//必须先右边先减,否则结束报错bug
while(i<j && nums[i]<=nums[left])++i;
swap(nums,i,j);
}
swap(nums,i,left); //把左边的位置放好
if(i>mid) return qSort(nums,left,i-1,mid);//看是否是中点,超过了中点,没超过就再次递归进入下一趟循环
if(i<mid) return qSort(nums,i+1,right,mid);//递归就是,进入下一次循环
return nums[mid];//是中点
}
public void swap(int[] nums,int x,int y) {//多次用的方法要封装
int tmp = nums[x];
nums[x]=nums[y];
nums[y]=tmp;
}
}
快排原理图,最后要把ij和 l最左边交换值,才完成一次快排
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
Arrays.sort(arr);
for (int i = 0; i < k; ++i) {
vec[i] = arr[i];
}
return vec;
}
}
优先队列的比较(大根堆排序)
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {//输入k目标变量
int[] vec = new int[k];
if (k == 0) { // 排除 0 的情况
return vec;
}
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer num1, Integer num2) {
return num2 - num1;//降序排列 大根堆,Comparator<Integer>必须指定int
}
});//都在一个大括号里,比较器
for (int i = 0; i < k; ++i) {//先添加k个到队列,维护者k个最小的
queue.offer(arr[i]);//添加
}
for (int i = k; i < arr.length; ++i) {
if (queue.peek() > arr[i]) {//跟队列头最大的比较
queue.poll();//删除队头
queue.offer(arr[i]);//加入新数据
}
}
for (int i = 0; i < k; ++i) {//依次输出最小的k个
vec[i] = queue.poll();
}
return vec;
}
}
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
if (k == 0) { // 排除 0 的情况
return vec;
}
PriorityQueue<Integer> queue=new PriorityQueue<>((x,y)->(y-x));
for (int i = 0; i < k; ++i) {//先添加k个到队列,维护者k个最小的
queue.offer(arr[i]);
}
for (int i = k; i < arr.length; ++i) {
if (queue.peek() > arr[i]) {//跟队列头最大的比较
queue.poll();//删除队头
queue.offer(arr[i]);//加入新数据
}
}
for (int i = 0; i < k; ++i) {//依次输出最小的k个
vec[i] = queue.poll();
}
return vec;
}
}
比较必须指定int类型
经典快排=前递归+数组双指针
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 最后一个参数表示我们要找的是下标为k-1的数
return quickSearch(arr, 0, arr.length - 1, k - 1);
}
private int[] quickSearch(int[] nums, int lo, int hi, int k) {
// 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
int j = partition(nums, lo, hi);//快排
if (j == k) {
return Arrays.copyOf(nums, j + 1);
}
// 否则根据下标j与k的大小关系来决定继续切分左段还是右段。调不同参数的同一个函数就是递归。
return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
}
// 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
private int partition(int[] nums, int lo, int hi) {
int v = nums[lo];
int i = lo, j = hi + 1;
while (true) {
while (++i <= hi && nums[i] < v);
while (--j >= lo && nums[j] > v);
if (i >= j) {
break;
}
int t = nums[j];
nums[j] = nums[i];
nums[i] = t;
}
nums[lo] = nums[j];
nums[j] = v;
return j;¥¥ij是相等的
}
}
优先队列的使用+中位数分奇偶两种情况来维护大小优先队列
堆排序:大根堆小根堆的使用,奇偶两种情况。。。要理解代码背后的含义概念,背会概念
class MedianFinder {
// 大顶堆存储较小一半的值
PriorityQueue<Integer> maxHeap;
// 小顶堆存储较大一般的值
PriorityQueue<Integer> minHeap;
/** initialize your data structure here. */
public MedianFinder() {
maxHeap = new PriorityQueue<Integer>((x, y) -> (y - x));//大顶堆的实现
minHeap = new PriorityQueue<Integer>();
}
//增加的方法
public void addNum(int num) {
// 长度为奇数时先放入小顶堆,重新排序后在插入到大顶堆
if(maxHeap.size() != minHeap.size()) {
minHeap.add(num);//加进去就能重新排好
maxHeap.add(minHeap.poll());
} else {
// 长度为偶数时先放入大顶堆,重新排序后在插入到小顶堆
maxHeap.add(num);
minHeap.add(maxHeap.poll());
}
}
public double findMedian() {
//分为奇偶两种情况
if(maxHeap.size() != minHeap.size()) {
return minHeap.peek();
} else {
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
}
递归分解思想(动态规划)+比较选最大类型的规律
class Solution {
public int maxSubArray(int[] nums) {//直接用nums当dp
int res = nums[0];//初始值0,
for(int i = 1; i < nums.length; i++) {//遍历从1开始,0是初始值
nums[i] += Math.max(nums[i - 1], 0);//规律选最大
res = Math.max(res, nums[i]);
}
return res;
}
}
数组规律,单变量最大值
class Solution {
public int maxSubArray(int[] nums) {
int length = nums.length;
// 最大总和值
int res = Integer.MIN_VALUE;//不能为0
// 当前总和
int curSum = 0;
for (int i = 0; i < length; i++) {
if (curSum < 0) {
// 如果 i 之前总和值小于0,那就从 i 开始重新计算
curSum = nums[i];
} else {
// 否则加上当前的值
curSum += nums[i];
}
// 寻找最大值
if (curSum > res) {
res = curSum;
}
}
return res;
}
}
第二次
class Solution {
public int maxSubArray(int[] nums) {
int length=nums.length;
int res=nums[0];
int curSum=0;
for (int i = 0; i < length; i++) {
if(curSum<0)curSum=nums[i];
else curSum+=nums[i];
if(curSum>res)res=curSum;//curSum是变化的指针,而res是最大值变量。
}
return res;
}
}
递归分解法
找规律用分解法:对象分解成多个部分+结果分解成多种情况(多个步骤)
class Solution {
public int countDigitOne(int n) {
//高位
int high = n;
//低位
int low = 0;
//当前位
int cur = 0;
int count = 0;
int num = 1;
while (high != 0 || cur != 0) {
cur = high % 10;
high /= 10;
//这里我们可以提出 high * num 因为我们发现无论为几,都含有它
if (cur == 0) count += high * num;
else if (cur == 1) count += high * num + 1 + low;
else count += (high + 1) * num;
//低位
low = cur * num + low;
num *= 10;
}
return count;
}
}
找规律的方法:代入例子,分变量列,分步列出,最后找出规律
class Solution {
public int findNthDigit(int n) {
int digit = 1;
long start = 1;初始值三个
long count = 9;
while (n > count) { // 1.
n -= count;//剩下多少位了,含义要确定。
digit += 1;
start *= 10;
count = digit * start * 9;
}
long num = start + (n - 1) / digit; // 2.确定该数字,举例输入13,得到num=11,n=4
return Long.toString(num).charAt((n - 1) % digit) - '0'; // 3.确定该数字的第几个,代码都有确切含义,都翻译过来。n代表剩下多少位了。Long转化数字为字符串。
}
}
字符串比较大小(大数问题)、前操作(数组双指针操作)递归(快排排序,每次把一个放在正确位置上)
class Solution {
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for(int i = 0; i < nums.length; i++)
strs[i] = String.valueOf(nums[i]);
quickSort(strs, 0, strs.length - 1);
StringBuilder res = new StringBuilder();//存结果
for(String s : strs)
res.append(s);
return res.toString();
}
void quickSort(String[] strs, int l, int r) {$$数组双指针
if(l >= r) return;//不进行任何操作,停止条件,一个元素就不用比较交换位置了
int i = l, j = r; //l不是1.
String tmp = strs[i];
while(i < j) {//前操作,换位置
while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) j--;//&& i < j不用他就会一直移动越界
while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) i++;
tmp = strs[i];//换i j变量的值
strs[i] = strs[j];
strs[j] = tmp;
}
strs[i] = strs[l];//换i l的值
strs[l] = tmp;
quickSort(strs, l, i - 1);//递归调用,换不同参数,就跟二叉树一样,左右
quickSort(strs, i + 1, r);//左右递归
}
}
排序工具+比较工具+字符串
class Solution {
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for(int i = 0; i < nums.length; i++)//数组转化成字符串组
strs[i] = String.valueOf(nums[i]);//int转字符串
Arrays.sort(strs, (x, y) -> (x + y).compareTo(y + x));//排序,升序排序,结果>0说明y>x
StringBuilder res = new StringBuilder();
for(String s : strs)
res.append(s);
return res.toString();
}
}
分解法找规律:动态规划,分情况的菲薄那且数列
分解法:其实就是边界例子+一般例子
class Solution {
public int translateNum(int num) {
String str = String.valueOf(num);
// 使用str.length()+1
int[] dp = new int[str.length()+1];
// 我们只能确定第一位有几种翻译方法
// 真正是从 1 开始,初始化 0 是用来来防止 i-2 出现 -1 的情况
dp[0] = 1;//边界
dp[1] = 1;
for (int i = 2; i <= str.length(); i++) {//范围,遍历完。考虑开始和结束
// 如果是两位数的话,需要在10~25之间才有效,否则只能翻译一个数字
if (str.substring(i-2, i).compareTo("10") >= 0 && str.substring(i-2, i).compareTo("25") <= 0) {//字符串比较大小
dp[i] = dp[i-1] + dp[i-2];
} else {
dp[i] = dp[i-1];
}
}
return dp[str.length()];
}
}
优化
class Solution {
public int translateNum(int num) {
String str = String.valueOf(num);
// 我们只能确定第一位有几种翻译方法
int a = 1;
int b = 1;
for (int i = 2; i <= str.length(); i++) {
// 如果是两位数的话,需要在10~25之间才有效,否则只能翻译一个数字
if (str.substring(i-2, i).compareTo("10") >= 0 && str.substring(i-2, i).compareTo("25") <= 0) {¥直接字符串比较大小
int temp = a + b;
a = b;
b = temp;
} else {
a = b;
}
}
return b;
}
}
二维数组dp+举出四个例子(三个边界)(分解法找规律):动态规划
class Solution {
public int maxValue(int[][] grid) {
int m = grid.length, n = grid[0].length;
for(int i = 0; i < m; i++) {//首先都是遍历
for(int j = 0; j < n; j++) {
if(i == 0 && j == 0) continue;//
if(i == 0) grid[i][j] += grid[i][j - 1] ;//特殊边界条件
else if(j == 0) grid[i][j] += grid[i - 1][j];
else grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);//else 不加else则每次进来都执行,不是分支语句了
}
}
return grid[m - 1][n - 1];//最后一个返回
}
}
class Solution {
public int maxValue(int[][] grid) {
int m=grid.length,n=grid[0].length;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0&&j==0) grid[i][j]=grid[0][0];//不用continue
else if(i == 0) grid[i][j] += grid[i][j - 1] ;// 这里不用continue 必须都是else if 否则执行完上一句还会来判断这句。
else if(j==0)grid[i][j]+=grid[i-1][j];
else grid[i][j]+=Math.max(grid[i][j-1],grid[i-1][j]);
}
}
return grid[m-1][n-1];
}
}
以上代码逻辑清晰,和转移方程直接对应,但仍可提升效率:当 gridgrid 矩阵很大时, i = 0i=0 或 j = 0j=0 的情况仅占极少数,相当循环每轮都冗余了一次判断。因此,可先初始化矩阵第一行和第一列,再开始遍历递推。
分情况的分解法(动态规)+hashMap映射表记录+变量结果
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();//保存char的位置,维护位置。
int res = 0, tmp = 0;//设置最大值结果,动态值
for(int j = 0; j < s.length(); j++) {
int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i,不是getIndexof
dic.put(s.charAt(j), j); // 更新哈希表,覆盖了原来的char位置
tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]维护的是临时距离,,tmp < j - i 判断关键,找规律
res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
}
return res;
}
}
错误代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character,Integer> dic=new HashMap<>();
int res = 0, tmp = 0;//设置全局最大值结果,临时结果值
for(int j = 0; j < s.length(); j++) {
//没有就加入
if(dic.getOrDefault(s.charAt(j),-1)==-1){
dic.put(s.charAt(j),j);
tmp++;
}
//如果有了
else{
int d=j-dic.get(s.charAt(j));
dic.put(s.charAt(j),j);//维护 不是set
tmp = tmp <d ? tmp + 1 :d;
if(res<tmp)res=tmp;
else tmp=1;
}
// 判断最大值是否变化
if(tmp>res)res=tmp;
}
return res;
}
}
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();//保存char的位置,维护位置。
int res = 0, tmp = 0;//设置最大值结果,动态值
for(int j = 0; j < s.length(); j++) {
int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i,不是getIndexof
dic.put(s.charAt(j), j); // 更新哈希表,覆盖了原来的char位置
if(tmp>=j-i){//这才是判断依据,存在不是判断依据,两种情况
tmp=j-i;
}
else tmp++;
res = Math.max(res, tmp);
}
return res;
}
}
多个参数共同决定一个dp,
public int nthUglyNumber(int n) {
int[] result = new int[n];
int n2 = 0,n3 = 0,n5 = 0;
result[0] = 1;//边界,可代入例子来理解代码
for (int i = 1; i < n; i++) {//i不参与运算
int num2 = result[n2] * 2;//result是核心操作对象,相当于给res中的每个数轮番乘以235比大小
int num3 = result[n3] * 3;
int num5 = result[n5] * 5;
int min = Math.min(Math.min(num2, num3), num5);//找出最小的
result[i] = min;
if (min == num2) n2 ++;
if (min == num3) n3 ++;
if (min == num5) n5 ++;
}
return result[n-1];
}
利用set 小根堆(头进尾巴出) +循环
class Solution {
private int[] uglyNumber = {2,3,5};
public int nthUglyNumber(int n) {
Set<Long> set = new HashSet<>();
//创建小根堆,每次出堆的都是最小值
PriorityQueue<Long> queue = new PriorityQueue<>();
queue.add(1L);
//记录出堆的个数,出堆的元素完全按照从小到大排序
int count = 0;
while (! queue.isEmpty()){
long cut = queue.poll();
//如果出堆的个数>=n,当前cut就是第n个丑数
if(++count >= n){//++count == n 也行,循环退出条件
return (int) cut;
}
for(int num : uglyNumber){
//排除重复的数字
if(! set.contains(num * cut)){
queue.add(num * cut);
set.add(num * cut);
}
}
}
return -1;
}
}
class Solution {
private int[] uglyNumber = {2,3,5};
public int nthUglyNumber(int n) {
PriorityQueue<Long> queue = new PriorityQueue<>();
Set<Long> set = new HashSet<>();
queue.add(1L);
int count = 1;//从1开始计数,他是停止变量
while (! queue.isEmpty()){//这个循环灵活
long tmp = queue.poll();
if(count == n){
return (int) tmp;
}
count++;
for(int num:uglyNumber){
if(!set.contains(num*tmp)){
set.add(num*tmp);
queue.add(num*tmp);
}
}
}
return -1;
}
}
1.基本类型:long,int,byte,float,double
2.对象类型:Long,Integer,Byte,Float,Double其它一切java提供的,或者你自己创建的类。
hashmap计数
class Solution {
public char firstUniqChar(String s) {
Map<Character,Integer> frequency=new HashMap<Character,Integer>();
for(int i=0;i<s.length();i++){
char ch=s.charAt(i);
frequency.put(ch,frequency.getOrDefault(ch,0)+1);//计数
}
for(int i=0;i<s.length();i++){
if(frequency.get(s.charAt(i))==1){
return s.charAt(i);
}
}
return ' ';
}
}
自己写的
class Solution {
public char firstUniqChar(String s) {
Map<Character,Integer> frequency=new HashMap<Character,Integer>();
for(int i=0;i<s.length();i++){
if(frequency.containsKey(s.charAt(i)) ){
frequency.put(s.charAt(i),frequency.get(s.charAt(i))+1);//indexOf返回string,charAt是字符
}
else
frequency.put(s.charAt(i),1);//put不是add
}
for(int i=0;i<s.length();i++){
if(frequency.get(s.charAt(i))==1)return s.charAt(i);
}
return ' ';
}
}
没法用循环了
典型后操作(归并操作)递归(考虑极端例子和普通) +辅助数组+考虑剩余的例子
public class MergeSort {
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high - low + 1];//用到辅助数组,暂存序列的
int i = low;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 把较小的数先移到新数组中
while (i <= mid && j <= high) {
if (a[i] < a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while (j <= high) {
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for (int k2 = 0; k2 < temp.length; k2++) {
a[k2 + low] = temp[k2];//递归从low开始设置进去,辅助的位置赋值给真正的位置。
}
}
public static void mergeSort(int[] a, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {//也是递归终止条件,=就不做任何事情,带入边界例子当low=high,代入mergeSort则不进行任何操作;当low+1=high时,会把这两个数字合并起来。
// 左边递归
mergeSort(a, low, mid);
// 右边递归
mergeSort(a, mid + 1, high);
// 左右归并
merge(a, low, mid, high);//递归操作:左右合起来,同一个数组a不用多加
System.out.println(Arrays.toString(a));
}
}
public static void main(String[] args) {
int a[] = { 51, 46, 20, 18, 65, 97, 82, 30, 77, 50 };
mergeSort(a, 0, a.length - 1);
System.out.println("排序结果:" + Arrays.toString(a));
}
}
从分的最后开始排列:理清楚递归的次序,先从7(i) 3(j)开始,第二次是2 6,第三次是3726 第四次是0 1
后(逆序操作,边界情况考虑)递归(两种例子)
归并应用具体情况
class Solution {
int[] nums, tmp;
public int reversePairs(int[] nums) {
this.nums = nums;
tmp = new int[nums.length];
return mergeSort(0, nums.length - 1);
}
private int mergeSort(int l, int r) {
// 终止条件
if (l >= r) return 0;
// 递归划分
int m = (l + r) / 2;
int res = mergeSort(l, m) + mergeSort(m + 1, r);
// 合并阶段,没有单独当成方法
int i = l, j = m + 1;//指针
for (int k = l; k <= r; k++)
tmp[k] = nums[k];
for (int k = l; k <= r; k++) {//遍历num的 lr区域并赋值的过程
//分情况看,两种例子:结束和普通
if (i == m + 1)//左边结束了,右边加进去
nums[k] = tmp[j++];
else if (j == r + 1 || tmp[i] <= tmp[j])//顺序和右边结束了合并在了一起。
nums[k] = tmp[i++];
else {//逆序
nums[k] = tmp[j++];
res += m - i + 1; // 统计逆序对
}
}
return res;
}
}
分清指针和变量的意义,画图,数组进行遍历
class Solution {
int[] nums, tmp;int res;
public int reversePairs(int[] nums) {
this.nums = nums;
tmp = new int[nums.length];
return mergeSort(0, nums.length - 1);
}
private int mergeSort(int l, int r) {
// 终止条件
if (l >= r) return 0;
// 递归划分
int m = (l + r) / 2;
int res = mergeSort(l, m) + mergeSort(m + 1, r);
res=merge(l,r,m,res);//res+=重复加了,,r不是m+1。l r是本次合并的范围,res变量代表每次逆序和
return res;
}
private int merge(int l, int r,int m,int res) {
int i = l, j =m+1;//指针,分别代表了合并左边数组和右边数组的指针,j不是r
for (int k = l; k <= r; k++)
tmp[k] = nums[k];//tmp代表了数据备份
for (int k = l; k <= r; k++) {//对当前范围的数组进行遍历
//分情况看,两种例子:结束和普通
if (i == m + 1)//左边结束了,右边加进去
nums[k] = tmp[j++];
else if (j == r + 1 || tmp[i] <= tmp[j])//顺序和右边结束了合并在了一起。
nums[k] = tmp[i++];
else {//逆序
nums[k] = tmp[j++];
res += m - i + 1; // 统计逆序对
}
}
return res;
}
}
图解法找规律,就一个例子即可
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
let a = headA,
b = headB;
while (a != b) {
// a 走一步,如果走到 headA 链表末尾,转到 headB 链表
a = a != null ? a.next : headB;//两种情况可用逻辑符号
// b 走一步,如果走到 headB 链表末尾,转到 headA 链表
b = b != null ? b.next : headA;
}
return a;
};
超时
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA,
b = headB;
while (a != b) {
if(a.next!=null)a=a.next;
else a=headB;
if(b.next!=null)b=b.next ;
else b=headA;
}
return a;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//统计链表A和链表B的长度
int lenA = length(headA), lenB = length(headB);
//如果节点长度不一样,节点多的先走,直到他们的长度一样为止
while (lenA != lenB) {
if (lenA > lenB) {
//如果链表A长,那么链表A先走
headA = headA.next;
lenA--;
} else {
//如果链表B长,那么链表B先走
headB = headB.next;
lenB--;
}
}
//然后开始比较,如果他俩不相等就一直往下走
while (headA != headB) {
headA = headA.next;
headB = headB.next;
}
//走到最后,最终会有两种可能,一种是headA为空,
//也就是说他们俩不相交。还有一种可能就是headA
//不为空,也就是说headA就是他们的交点
return headA;
}
//统计链表的长度
private int length(ListNode node) {
int length = 0;
while (node != null) {
node = node.next;
length++;
}
return length;
}
前(二分查找)操作 二次递归(根据需求) 。循环就等于递归,只是形式不一样。
//也是递归,递归就是调用不同参数的相同方法。
int cnt = 0; // 计数器count
public int search(int[] nums, int target) {
// 初始化low = 0, high = nums.length - 1
helper(nums, target, 0, nums.length - 1);
return cnt;
}
// 根据算法设计分3种情况,分情况,分步奏
public void helper(int[] nums, int target, int low, int high) {
if (low <= high) { //停止条件例子,两个指针相等
int mid = (high - low) / 2 + low;
if (nums[mid] == target) {
cnt++; // 计数一次
helper(nums, target, low, mid - 1);
helper(nums, target, mid + 1, high);
} else if (nums[mid] > target) {
helper(nums, target, low, mid - 1);//没用循环,全部都是递归
} else {
helper(nums, target, mid + 1, high);
}
}
}
纯递归实现,递归改了参数调用自己,
class Solution {
int cnt=0;
public int search(int[] nums, int target) {
helper(nums, target, 0, nums.length - 1);
return cnt;
}
public void helper(int[] nums, int target, int low, int high) {
if(low > high)return;
int mid = (high - low) / 2 + low;
if (nums[mid] == target) {
cnt++; // 计数一次
helper(nums, target, low, mid - 1);
helper(nums, target, mid+1, high);
}
else if(nums[mid] > target)
helper(nums, target,low, mid - 1);//low不是0
else helper(nums, target, mid+1,high);
}
}
自己写的
public void helper(int[] nums, int target, int low, int high) {
if(low<=high){
int mid=(low+high)/2;
if(nums[mid]<target)
helper(nums,target,mid+1,high);
else if(nums[mid]>target)
helper(nums,target,low,mid-1);
else{
cnt++;
if(mid+1<=high&&nums[mid+1]==target)
helper(nums,target,mid+1,high);
if(mid-1>=low&&nums[mid-1]==target)
helper(nums,target,low,mid-1);
}
}
}
循环实现+数组指针和值的关系+数组双指针(二分法)+图解法
循环和递归:结束条件+指针维护+初始条件,
区别在于
1.递归是多次n次循环,循环是一次,当遇到三重循环以上 而且返回数值 要用递归
2.递归是可以实现(操作和递归的)正序逆序,最后执行结束if语句;循环只能正序,不能倒着来。
3.他们的结束语句不太一样,循环是结束条件while(i>j)+if语句,迭代是if
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j) {//括号里面的 相当于递归的终止条件(相反),递归过程就是循环
int m = (i + j) / 2;//相当于递归里的操作
if(nums[m] == m) i = m + 1;//相当于递归过程,改变参数
else j = m - 1;
}
return i;//i刚好是空出来的位置的索引
}
}
递归实现+二分法+缺失具体例子
class Solution {
public int missingNumber(int[] nums) {
//二分
return binarySearch(nums, 0, nums.length - 1);//初始条件
}
public int binarySearch(int[] nums, int left, int right){
if(left >= right){ //递归终止条件,这是最后执行的,返回
//此时分两种情况。要举出例子才能想得到。。。
//1.如果跟左边相等是说明是右边缺失
if(nums[left] == left){
return left+1;
//2.不等肯定是左边缺失啦
}else{
return left;
}
}
int mid = left + (right-left)/2;
System.out.println(mid);
//相等说明左序列正常,往右找
if(nums[mid] == mid){
return binarySearch(nums, mid + 1, right);//指针维护i++一样,这是最先执行的
}else{
return binarySearch(nums, left, mid - 1);
}
}
}
后序遍历(三种边界例子)
代码自己默背默写一遍。报错就重新理一遍,看哪里代码逻辑有问题。
class Solution {
int res, k;
public int kthLargest(TreeNode root, int k) {
this.k = k;
dfs(root);
return res;//返回全局变量作为结果
}
void dfs(TreeNode root) {
if(root == null) return;//边界条件
dfs(root.right);//先右子树从大到小
if(k == 0) return;//极端例子
if(--k == 0) res = root.val;//正常结束的例子
dfs(root.left);
}
}
典型后递归有返回值(递归到极端例子后返回的值对比之后再+1返回)(实质还是两种例子)
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;//空节点极端例子
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;//普通节点例子下:都要比较最大值
}
}
队列实现( 广度遍历,递归也可以实现)+辅助队列+计算深度
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
List<TreeNode> queue = new LinkedList<>() {{ add(root); }}, tmp;
int res = 0;//存结果的
while(!queue.isEmpty()) {
tmp = new LinkedList<>();
for(TreeNode node : queue) {//把队列都清空才深度加1
if(node.left != null) tmp.add(node.left);
if(node.right != null) tmp.add(node.right);
}
queue = tmp;//最后在赋值给队列,辅助队列
res++;
}
return res;
}
}
后操作(比较深度)递归有返回(两种极端例子 )
class Solution {
public boolean isBalanced(TreeNode root) {
return recur(root)!=-1;
}
public int recur(TreeNode root){
//三种结果
if(root==null)return 0;//空的极端例子
int left=recur(root.left);
if(left==-1)return -1;//极端例子,左子树不是平衡树的例子$
int right=recur(root.right);
if(right==-1)return -1;
//返回当前节点的深度
return Math.abs(left-right)<2?Math.max(left,right)+1:-1;$//返回深度或者-1
}
}
class Solution {
public boolean isBalanced(TreeNode root) {
return recur(root)!=-1;
}
public int recur(TreeNode root){
//三种结果
if(root==null)return 0;//空的极端例子
int left=recur(root.left);
if(left==-1)return -1;//极端例子,左子树不是平衡树的例子$
int right=recur(root.right);
if(right==-1)return -1;
//返回当前节点的深度
int b=0;
if(left>right)
b=left-right;
else
b=right-left;
if(b>1)return-1;
else
return 1+Math.max(left,right);
}
}
找出规律:与操作和异或操作的规律,单个例子的分步执行来找出规律。
例子的分类和分步两个属性。
class Solution {
public int[] singleNumbers(int[] nums) {
int x = 0, y = 0, n = 0, m = 1;
for(int num : nums) // 1. 遍历异或
n ^= num;$
while((n & m) == 0) // 2. 循环左移,计算 m
m <<= 1;
for(int num: nums) { // 3. 遍历 nums 分组$
if((num & m) != 0) x ^= num; // 4. 判断m所在的位置是否为1分为两组,这是与的特点
else y ^= num; // 4. 当 num & m == 0
}
return new int[] {x, y}; // 5. 返回出现一次的数字
}
}
变量循环改变
可以将程序算一遍:双循环结构,可以改编程序,要将程序结构理解掌握并能改写。
class Solution {
public int singleNumber(int[] nums) {
int ans=0;//返回的结果
int bit=0;
for(int i=31;i>=0;i--){//int32位,因为不知道Int是多少位,从高位算到低位。拉皮条一样。
for(int j=0;j<nums.length;j++){//算某一位和 $
bit+=nums[j]>>i&1;//算某一位置上的和 31,&1是判断最低位是不是1..$
}
ans=ans*2+bit%3;//从高位算到低位。最后一次才是低位。直接就把结果算出来了(也可以存一个结果数组)。$
bit=0;
}
return ans;
}
}
双指针单循环
class Solution {
public int[] twoSum(int[] nums, int target) {
int i = 0, j = nums.length - 1;
while(i < j) {
int s = nums[i] + nums[j];$
if(s < target) i++;
else if(s > target) j--;
else return new int[] { nums[i], nums[j] };//返回新建的数组$
}
return new int[0];
}
}
三种情况
双指针单循环的进一步应用
public int[][] findContinuousSequence(int target) {
int i = 1; // 滑动窗口的左边界
int j = 1; // 滑动窗口的右边界
int sum = 0; // 滑动窗口中数字的和
List<int[]> res = new ArrayList<>();//结果为数组 list$
while (i <= target / 2) {//总控制
//分步奏,先加后减
if (sum < target) {
// 右边界向右移动
sum += j;
j++;
} else if (sum > target) {
// 左边界向右移动
sum -= i;
i++;
} else {
// 记录一次结果
int[] arr = new int[j-i];$
for (int k = i; k < j; k++) {
arr[k-i] = k;¥是k,要的是索引
}
res.add(arr);
// 记录完以后,左边界向右移动,再进行下一次,去掉一个最小值
sum -= i;¥
i++;
}
}
return res.toArray(new int[res.size()][]);
}
字符串StringBuilder操作:for循环
class Solution {
public String reverseWords(String s) {
String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
StringBuilder res = new StringBuilder();
for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
if(strs[i].equals("")) continue; // 遇到空单词则跳过
res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
}
}
递归就是分解法:一分为二。
后操作递归(看似在一起,其实是后操作)+两种递归例子和边界例子。(递归就是写清楚各种例子)
class Solution {
public String reverseWords(String s) {
String[] s1 = s.split(" ");//分割字符串,数组就是递归的对象
String s2 = reverseWords1(s1, s1.length - 1);//调用递归
if(s2.length() > 0){
return s2.substring(0, s2.length() - 1);//去掉空格
} else {
return s2;
}
}
public String reverseWords1(String[] s, int index){
if (index == -1){//结束返回
return "";¥返回空字符
} else {
if (s[index].equals("")){//空格的情况,直接返回¥
return reverseWords1(s, index - 1);
} else {
return s[index] + " " + reverseWords1(s, index - 1);//不是空格的情况,返回字符串和递归¥
}
}
}
}
后操作递归(看似在一起,其实是后操作)+两种递归例子和边界例子。(递归就是写清楚各种例子)
class Solution {
public String reverseLeftWords(String s, int n) {
if (n == 0){//循环结束,返回结果
return s;
} else {
return reverseLeftWords(s.substring(1, s.length()) + s.substring(0,1), n - 1);//循环处理:递归每一次把一个首字符放在最后。递归调用:就跟循环的每次步进一样。递归就是循环。
}
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
if (n == 0){//循环结束,返回结果
return s;
} else {
return reverseLeftWords(s.substring(1,s.length())+s.charAt(0),n-1) ;//循环处理:递归每一次把一个首字符放在最后。递归调用:就跟循环的每次步进一样。递归就是循环。
}
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
if (n == 0){//循环结束,返回结果
return s;
} else {
return s.substring(n,s.length()) + s.substring(0,n);//循环处理:递归每一次把一个首字符放在最后。递归调用:就跟循环的每次步进一样。递归就是循环。
}
}
}
、
deque双向队列维护+大小等于三种例子+int[]存结果(i为指针,每个容器都有自己的指针,指针就是变量)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 || k == 0) return new int[0];¥空
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];//算个数¥
for(int j = 0, i = 1 - k; j < nums.length; i++, j++) {//i是为了计算窗口形成后,数组的指针
// 删除 deque 中对应的 nums[i-1]
if(i > 0 && deque.peekFirst() == nums[i - 1])//等于的情况
deque.removeFirst();
// 保持 deque 递减
while(!deque.isEmpty() && deque.peekLast() < nums[j])//大于的情况,这是个循环不断地删除最后一个小于它的。这样就维护了第一个最大,不用排序了。
deque.removeLast();
deque.addLast(nums[j]);//小于的情况和大于情况都加进去,都加进去不分情况,任何情况¥
// 记录窗口最大值
if(i >= 0)
res[i] = deque.peekFirst();//保存结果,getfist就给删掉了¥
}
return res;
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
// 未形成窗口
for(int i = 0; i < k; i++) {
while(!deque.isEmpty() && deque.peekLast() < nums[i])
deque.removeLast();
deque.addLast(nums[i]);
}
res[0] = deque.peekFirst();
// 形成窗口后
for(int i = k; i < nums.length; i++) {
if(deque.peekFirst() == nums[i - k])//前面删除,出了窗口期了
deque.removeFirst();
while(!deque.isEmpty() && deque.peekLast() < nums[i])//判断空
deque.removeLast();
deque.addLast(nums[i]);
res[i - k + 1] = deque.peekFirst();//未形成窗口期时不保存结果。
}
return res;
}
}
集合代表的意义和维护+一个数一个属操作的情况:从压入第一个开始编写代码,第1 2 3 4 个+时间与空间转换
class MaxQueue {
Queue<Integer> queue;//为维护一个弹窗队列
Deque<Integer> deque;//维护一个最大值双向队列,也就是数据库维护
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();//实现
}
public int max_value() {//返回最大值
return deque.isEmpty() ? -1 : deque.peekFirst();
}
public void push_back(int value) {//压入一个,传入参数value
queue.offer(value);//队列压入
while(!deque.isEmpty() && deque.peekLast() < value)//双向队列删除之前的小于val的值,还要加上不为空的情况(非第一个)
deque.pollLast();
deque.offerLast(value);//双向队列压入
}
public int pop_front() {//弹出第一个
if(queue.isEmpty()) return -1;¥¥
if(queue.peek().equals(deque.peekFirst()))//如果这个是最大值,更新维护最大值队列
deque.pollFirst();
return queue.poll();//弹出
}
}
递归(举例子)
class Solution {//找规律:s共有 6^n种情况,共有5*n+1种不重复结果
public double[] twoSum(int n) {
//思路一:遍历 5*n+1种不重复结果,计算每一种不重复结果的组合个数
double[] res=new double[5*n+1];
int curSum=n;
for(int i=0; i<res.length; ++i){
res[i]=countSum(curSum, n)/Math.pow(6, n);
++curSum;
}
return res;
}
int countSum(int curSum, int n){
if(n<0 || curSum<0){return 0;}
if(n==0&&curSum==0){return 1;}//出口
int sum=0;
for(int i=1; i<7; ++i){
sum+=countSum(curSum-i, n-1);//循环
}
return sum;
}
}
把n=1 2 (也是n和n-1)的例子写成代码
三层循环(2+1)(每层循环的意义)+dp辅助数组的变化维护+(递归循环)+举例子:n=1.2
class Solution {
public double[] dicesProbability(int n) {
double[] dp = new double[6];//初始结果
Arrays.fill(dp, 1.0 / 6.0);//第一次的例子
for (int i = 2; i <= n; i++) {//1.筛子数遍历
//新建空的当前筛子数的临时结果
double[] tmp = new double[5 * i + 1];
for (int j = 0; j < dp.length; j++) {//2.遍历上次的dp结果
for (int k = 0; k < 6; k++) {//3.比上次多加了6种可能性,核心操作,每次的增加算法。递推公式
tmp[j + k] += dp[j] / 6.0;
}
}
dp = tmp;//上一次的赋值给dp了,dp就是结果,更新维护dp
}
return dp;
}
}
三种针对题中对象例子:大小王和无大小王和重复问题
class Solution {
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();//根据规律选择容器
int max = 0, min = 14;
for(int num : nums) {//遍历每一个数字
if(num == 0) continue; // 跳过大小王
max = Math.max(max, num); // 最大牌
min = Math.min(min, num); // 最小牌
if(repeat.contains(num)) return false; // 若有重复,提前返回 false
repeat.add(num); // 添加此牌至 Set
}
return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}
分解法:::从n=0 1 2 3 4去分析完整例子,推导出得出规律。
要用递归思路去分析规律题。
后操作递归有返回值(利用了上一层递归的值)
class Solution {
public int lastRemaining(int n, int m) {
return f(n, m);
}
public int f(int n, int m) {
if (n == 1) {
return 0;//假设n=1时
}
int x = f(n - 1, m);//代表上一次的位置
return (m + x) % n;//上一次的位置+m %n,,,n不是一直是5,递减的
}
}
class Solution {
public int lastRemaining(int n, int m) {
int f = 0;//假设只有一个数字时,是0
for (int i = 2; i != n + 1; ++i) {//n时递归变化的
f = (m + f) % i;
}
return f;
}
}
用链表去跑模拟
class Solution {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(i);//加数据
}
int idx = 0;
while (n > 1) {
idx = (idx + m - 1) % n;//去掉数据
list.remove(idx);
n--;
}
return list.get(0);
}
}
分解法+完整例子
最大收益公式规律:当前值-最小值和以往最大收益的比较值
class Solution {
public int maxProfit(int[] prices) {
int cost = Integer.MAX_VALUE, profit = 0;初始第0天
for(int price : prices) {
cost = Math.min(cost, price);//用一个变量记录最小值 并维护
profit = Math.max(profit, price - cost);//记录最大收益并维护,
}
return profit;
}
}
后递归返回值的用法和+逻辑运算符
class Solution {
public int sumNums(int n) {
boolean flag = n > 0 && (n += sumNums(n - 1)) > 0;//n<0就不算了,n不能等于0
return n;
}
}
class Solution {
public int sumNums(int n) {
try {
int[] a = new int[n];
} catch (NegativeArraySizeException e) {
return 0;
}
return n + sumNums(n - 1);
}
}
异或和与的概念
代入1 , 1
class Solution {
public int add(int a, int b) {
while(b != 0) { // 当进位为 0 时跳出
int c = (a & b) << 1; // c = 进位
a ^= b; // a = 非进位和
b = c; // b = 进位
}
return a;
}
}
图解法+分解法 自己手写一遍代码
分解过程上下三角
class Solution {
public int[] constructArr(int[] a) {
int len = a.length;
if(len == 0) return new int[0];
int[] b = new int[len];
b[0] = 1;
int tmp = 1;
//下三角规律
for(int i = 1; i < len; i++) {//i代表第几行
b[i] = b[i - 1] * a[i - 1];//减少了重复乘法
}
//上三角规律
for(int i = len - 2; i >= 0; i--) {//i代表第几行。。从length-1开始
tmp *= a[i + 1];//tmp一直在累乘,当作a[i]*a[i-1]*...
b[i] *= tmp;//这是要求的值
}
return b;
}
}
.
例子完整性+循环框架。
class Solution {
public int strToInt(String str) {
char[] c = str.trim().toCharArray();
if(c.length == 0) return 0;
int res = 0, bndry = Integer.MAX_VALUE / 10;
int i = 1, sign = 1;//存符号变量
if(c[0] == '-') sign = -1;//考虑到正负号
else if(c[0] != '+') i = 0;
for(int j = i; j < c.length; j++) {
if(c[j] < '0' || c[j] > '9') break;//其他符号
//大数情况上限了,分正负情况,/10是因为这是倒数第一个数字
if(res > bndry || res == bndry && c[j] > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = res * 10 + (c[j] - '0');//循环转换成数字
}
return sign * res;
}
}
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
List<TreeNode> path_p = getPath(root, p);
List<TreeNode> path_q = getPath(root, q);
TreeNode ancestor = null;
for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
if (path_p.get(i) == path_q.get(i)) {
ancestor = path_p.get(i);
} else {
break;
}
}
return ancestor;
}
public List<TreeNode> getPath(TreeNode root, TreeNode target) {
List<TreeNode> path = new ArrayList<TreeNode>();
TreeNode node = root;
while (node != target) {
path.add(node);
if (target.val < node.val) {
node = node.left;
} else {
node = node.right;
}
}
path.add(node);
return path;
}
}
三种例子
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null) {
if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
root = root.right; // 遍历至右子节点
else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
root = root.left; // 遍历至左子节点
else break;
}
return root;
}
}
//前后递归+成员变量存结果
class Solution {
private TreeNode ans;//结果
public Solution() {
this.ans = null;
}
private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return false;
boolean lson = dfs(root.left, p, q);
boolean rson = dfs(root.right, p, q);
if ((lson && rson) || ((root.val == p.val || root.val == q.val) && (lson || rson))) {//如果是在同一个子树,或者一个是本身另一个是子树,则成功找到
ans = root;
}
return lson || rson || (root.val == p.val || root.val == q.val);//返回左或者右子存在或者当前节点就是
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.dfs(root, p, q);
return this.ans;
}
}
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
int[][] matrix_new = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];//元素对应规律
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix[i][j] = matrix_new[i][j];
}
}
}
}
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < (n + 1) / 2; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
}
class Solution {
public List<String> letterCombinations(String digits) {
List<String> combinations = new ArrayList<String>();
if (digits.length() == 0) {
return combinations;
}
Map<Character, String> phoneMap = new HashMap<Character, String>() {{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
backtrack(combinations, phoneMap, digits, 0, new StringBuffer());
return combinations;
}
public void backtrack(List<String> combinations, Map<Character, String> phoneMap, String digits, int index, StringBuffer combination) {
if (index == digits.length()) {
combinations.add(combination.toString());
} else {
char digit = digits.charAt(index);
String letters = phoneMap.get(digit);
int lettersCount = letters.length();
for (int i = 0; i < lettersCount; i++) {
combination.append(letters.charAt(i));
backtrack(combinations, phoneMap, digits, index + 1, combination);
combination.deleteCharAt(index);
}
}
}
}
只看两个的,1个是初始条件。三个可以看成两个。这就是分解法找规律的思路。
循环好理解,递归不好理解。
class Solution {
//一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
//这里也可以用map,用数组可以更节省点内存
String[] letter_map = {" ","*","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
//注意边界条件
if(digits==null || digits.length()==0) {
return new ArrayList<>();
}
iterStr(digits, new StringBuilder(), 0);
return res;
}
//最终输出结果的list
List<String> res = new ArrayList<>();
//递归函数
void iterStr(String str, StringBuilder letter, int index) {
//递归的终止条件,注意这里的终止条件看上去跟动态演示图有些不同,主要是做了点优化
//动态图中是每次截取字符串的一部分,"234",变成"23",再变成"3",最后变成"",这样性能不佳
//而用index记录每次遍历到字符串的位置,这样性能更好
if(index == str.length()) {
res.add(letter.toString());
return;
}
//获取index位置的字符,假设输入的字符是"234"
//第一次递归时index为0所以c=2,第二次index为1所以c=3,第三次c=4
//subString每次都会生成新的字符串,而index则是取当前的一个字符,所以效率更高一点
char c = str.charAt(index);
//map_string的下表是从0开始一直到9, c-'0'就可以取到相对的数组下标位置
//比如c=2时候,2-'0',获取下标为2,letter_map[2]就是"abc"
int pos = c - '0';
String map_string = letter_map[pos];
//遍历字符串,比如第一次得到的是2,页就是遍历"abc"
for(int i=0;i<map_string.length();i++) {
//调用下一层递归,用文字很难描述,请配合动态图理解
letter.append(map_string.charAt(i));
//如果是String类型做拼接效率会比较低
//iterStr(str, letter+map_string.charAt(i), index+1);
iterStr(str, letter, index+1);
letter.deleteCharAt(letter.length()-1);
}
}
}
public int[] dailyTemperatures(int[] T) {
int length = T.length;
int[] result = new int[length];
for (int i = 0; i < length; i++) {
int current = T[i];
if (current < 100) {
for (int j = i + 1; j < length; j++) {
if (T[j] > current) {
result[i] = j - i;
break;
}
}
}
}
return result;
}