/*
二分查找时需要注意以下细节:
1.在计算 mid 时不能使用 mid = (low + high) / 2 这种方式, 因为 l + h 可能会导致加法溢
出, 应该使用 mid = l + (h - l) / 2。
2.对 h 的赋值和循环条件有关, 当循环条件为 l <= h 时, h = mid - 1; 当循环条件为 l <
h 时, h = mid。 解释如下: 在循环条件为 l <= h 时, 如果 h = mid, 会出现循环无法退
出的情况, 例如 l = 1, h = 1, 此时 mid 也等于 1, 如果此时继续执行 h = mid, 那么就
会无限循环; 在循环条件为 l < h, 如果 h = mid - 1, 会错误跳过查找的数, 例如对于数
组 [1,2,3], 要查找 1, 最开始 l = 0, h = 2, mid = 1, 判断 key < arr[mid] 执行
h = mid - 1 = 0, 此时循环退出, 直接把查找的数跳过了。
3.l的赋值一般都为 l = mid + 1。
*/
1. 二维数组中的查找
public class Solution {
public boolean Find(int target, int [][] array) {
if(array==null) return false;
//首先统计行列的长度
int row = array.length;
int col = array[0].length;
//从左下角开始判断
int i = row-1,j=0;
while(i>=0&&j<col){
if(array[i][j]<target){
j++;
}else if(array[i][j]>target){
i--;
}else{
return true;
}
}
return false;
}
}
2. 替换空格
public class Solution {
public String replaceSpace(StringBuffer str) {
String str1 = str.toString();
StringBuilder sb = new StringBuilder();
for(int i =0;i<str1.length();i++){
if(str1.charAt(i)==' '){
sb.append("%20");
}else{
sb.append(str1.charAt(i));
}
}
return sb.toString();
}
}
3. 从尾到头打印链表
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
ArrayList al = new ArrayList();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode == null){
return al;
}
Deque<Integer> stack = new ArrayDeque<>();
while(listNode != null){
stack.push(listNode.val);
listNode = listNode.next;
}
while(!stack.isEmpty()){
al.add(stack.pop());
}
return al;
}
public ArrayList<Integer> printListFromTailToHead2ListNode listNode) {
//相当于从尾到头访问一边链表,可以用栈来做
//也可以直接递归的时候打印
if(listNode != null){
printListFromTailToHead(listNode.next);
al.add(listNode.val);
}
return al;
}
}
4. 重建二叉树
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
return reBuilder(pre,0,pre.length-1,in,0,in.length-1);
}
public static TreeNode reBuilder(int pre[],int pleft,int pright,int in[],int ileft,int iright){
if(pleft>pright||ileft>iright) return null;
//先序列的第一个位置就是根节点
TreeNode cur = new TreeNode(pre[pleft]);
int i=0;
//寻找根节点在中序的位置
for(i = ileft;i<=in.length;i++){
if(pre[pleft] == in[i])
break;
}
//递归左子树进行恢复,由上面可以知道左子树的长度是i-ileft,则开始index=pleft+1,结束index=pleft+1+(i-ileft)-1=pleft+i-ileft
cur.left = reBuilder(pre,pleft+1,pleft+i-ileft,in,ileft,i-1);
//递归右子树进行恢复,显然在pre中右子树的开始索引就是前面左子树结束索引的下一个位置,所以就是pleft+i-ileft+1
cur.right = reBuilder(pre,pleft+i-ileft+1,pright,in,i+1,iright);
return cur;
}
}
5. 用两个栈实现队列
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
//先判断辅助栈是否为空,不为空先全部压入主栈中,然后将要添加的元素push到主栈中,再将主栈中全部元素push到辅助栈中
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
stack1.push(node);
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
public int pop() {
return stack2.pop();
}
}
6. 旋转数组的最小元素
//时间复杂度太高
public class Solution1 {
public int minNumberInRotateArray(int [] array) {
//为空或者元素个数为0返回0
if(array==null || array.length == 0)return 0;
for(int i = 1;i<array.length;i++){
if(array[i-1]>array[i])
return array[i];
}
//如果就是初始的没旋转则有头元素小于尾元素,直接返回首元素
return array[0];
}
}
/*既然是查找就可以用二分法(需要考虑有元素重复的问题)
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边
还是右边,这时只好一个一个试 ,
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左
边。因为右边必然都是递增的。
high = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6] array[low] = 4 ;
array[mid] = 4 ;
array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid
但情形(1)中low = mid + 1就不会错误
*/
public class Solution {
public int minNumberInRotateArray(int [] array) {
//为空或者元素个数为0返回0
if(array==null || array.length == 0)return 0;
int left = 0;
int right = array.length-1;
//先判断是否旋转过,没旋转直接返回首元素
if(array[0]<array[right]){
return array[0];
}
int mid;
while(left<right){
mid = left+(right-left)/2;
//array[mid]>array[right]则最小元素一定在右边
if(array[mid]>array[right]){
left = mid + 1;
//array[mid] == array[right]则需再判断
}else if(array[mid] == array[right]){
right--;
//array[mid]<array[right]则一定在左边
//如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字,如果high = mid - 1,
//就会产生错误, 因此high = mid
}else{
right = mid;
}
}
return array[left];
}
}
7. 斐波那契数组
//迭代时间复杂度很低
public class Solution {
public int Fibonacci(int n) {
int a=1,b=1,c=0;
if(n<0){
return 0;
}else if(n==1||n==2){
return 1;
}else{
for (int i=3;i<=n;i++){
c=a+b;
b=a;
a=c;
}
return c;
}
}
}
//直接用递归时间复杂度很高
public class Solution {
public int Fibonacci(int n) {
if(n<1)
return 0;
if(n==1||n==2)
return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
//终极动态规划版
public class Solution {
public int Fibonacci(int n) {
int f = 0, g = 1;
while(n-->0) {
g += f;
f = g - f;
}
return f;
}
}
8. 跳台阶
public class Solution {
public int JumpFloor(int target) {
//跟斐波那契一样,只不过不是从0开始只能从1开始
int f = 1, g = 2;
while(target-->1) {
g += f;
//前面的g已经别改变了,所以这里要把原来的g值赋给f只能用减法来做
f = g - f;
}
return f;
}
}
9. 变态跳台阶
public class Solution {
public int JumpFloorII(int target) {
//可以直接根据公式来推算
/*
f(n) = f(n-1) + f(n-2) + f(n-3) +...+f(1)
f(n-1) = f(n-2) + f(n-3) +...+f(1)
....
f(n) = 2*f(n-1)
*/
//这接用位移运算来做
int a = 1;
a = a << (target-1);
return a;
}
}
10. 矩阵覆盖
public class Solution {
public int RectCover(int target) {
//同理也是斐波那契的思路,但是序列却是0,1,2,3,5,8,.....
//对于2*n的大矩阵 只能是一个2*1箱子竖着放,或者两个2*1箱子平着放
if(target == 0) return 0;
if(target == 1) return 1;
int a = 1 , b = 1;
while(--target>0){
b += a;
a = b-a;
}
return b;
}
}
11. 二进制中1的个数
public class Solution {
public int NumberOf1(int n) {
//当一个数减一之后它的二进制从右到左的第一个1会变成0,而后面的都会变成1
//然后再和原来的数进行与运算就相当于把后面的1也变成了0所以这样就去掉了二进制中的一个1,循环知道全部去电1则变成0
int count=0;
while(n!=0){
n = n&(n-1);
count++;
}
return count;
}
}
12. 数值的整数次方
public double Power(double base, int exponent) throws Exception{
//首先判断底数不能为0且指数为0时结果为1
double result = 1;
if(base == 0){
return 0;
}
if(exponent == 0)
return result;
//将指数统一为正
int n = exponent>0?exponent:(-exponent);
//二分求解,然后平方,这里注意要Power大写
result = Power(base,n>>1);
result = result*result;
//判断奇偶性,是奇数还得再乘上一个base
if((n &0x1) == 1)
result = result*base;
//然后根据奇偶来确定结果
result = exponent>0?result:1/result;
return result;
}
13. 调整数组位置使奇数位于偶数前面,但是相对位置不能改变
//就是partition问题而已
//错误解法,顺序发生改变了
public class Solution {
public void reOrderArray(int [] array) {
//要求稳定
//可以利用快排的思路(但是快排是不稳定的)
//分别从两端遍历,left和right(这样的做法并不稳定)
int left = 0,right = array.length-1;
while(left<right){
//前半部分是否有偶数和后半部分是否有奇数
if(array[left]%2==0&&array[right]%2==1){
swap(array,left,right);
left++;
right--;
}else if(array[left]%2==0){
right--;
}else
left++;
}
}
public static void swap(int[] array,int a,int b){
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
}
//类似冒泡解法,当前偶数后奇数即相邻时偶奇就交换
public class Solution {
public void reOrderArray(int [] array) {
for(int i =0 ;i<array.length-1;i++){
for(int j=0;j<array.length-1-i;j++){
//前偶后奇就交换
if(array[j]%2==0&&array[j+1]%2==1){
swap(array,j,j+1);
}
}
}
}
public static void swap(int[] array,int a,int b){
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
}
/**
* 1.要想保证原有次序,则只能顺次移动或相邻交换。
* 2.i从左向右遍历,找到第一个偶数。
* 3.j从i+1开始向后找,直到找到第一个奇数。
* 4.将[i,...,j-1]的元素整体后移一位,最后将找到的奇数放入i位置,然后i++。
* 5.終止條件:j向後遍歷查找失敗。
*/
public class Solution {
public void reOrderArray(int [] array) {
if(array==null||array.length==0)
return;
int i = 0,j;
while(i<array.length){
//找到偶数
while(i<array.length&&array[i]%2==1)
i++;
j = i+1;
//然后从偶数后面开始找第一个奇数
while(j<array.length&&array[j]%2==0)
j++;
//将i到j-1后移一位然后将j放在i的位置
if(j<array.length){
int tmp = array[j];
for (int j2 = j-1; j2 >=i; j2--) {
array[j2+1] = array[j2];
}
array[i++] = tmp;
//后面全是偶数就不用动了
}else{
break;
}
}
}
}
14. 链表的倒数第k个节点
//倒数第K个结点就是从头结点开始的第n-k-1个结点。只要从头结点开始往后走n-k+1步就可以了。
//但是这里用栈也可以,就是需要空间复杂度为O(N)
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
//解法1 使用栈来做,先全都入栈,然后出栈第k个就是了
//节点为空或者k为0都返回空
if(head == null || k == 0) return null;
Deque<ListNode> stack = new ArrayDeque<>();
//统计个数
int count =0;
while(head!=null){
stack.push(head);
count++;
head = head.next;
}
//如果k比节点数多或者k==0则返回空
while(k>count){
return null;
}
//一次弹出前k-1个
while(k-->1){
stack.pop();
}
return stack.pop();
}
}
15. 反转链表
//递归方法
public class Solution {
public ListNode ReverseList(ListNode head) {
//1. 头插法
//2. 使用栈
//3. 递归做法
//只用一个节点或者节点为空,直接返回head
if(head == null||head.next==null) return head;
//先递归找到到链表的末端结点,从后依次反转整个链表
ListNode newHead = ReverseList(head.next);
//将H指向的地址赋值给head->next->next指针,并且一定要记得让head->next =null,
//也就是断开现在指针的链接,否则新的链表形成了环,下一层head->next->next赋值的时候会覆盖后续的值。
head.next.next = head;
head.next = null;
return newHead;
}
}
//就地逆置法
public class Solution {
public ListNode ReverseList(ListNode head) {
//只用一个节点或者节点为空,直接返回head
if(head == null||head.next==null) return head;
ListNode newHead = null ,cur = head;
//从头开始处理到尾
while(cur!=null){
//保存cur的next节点
ListNode temp = cur.next;
//使当前节点next指向前面处理后的头结点
cur.next = newHead;
//这样现在当前节点就应该是新的头节点
newHead = cur;
//接着处理,让当前节点后移到保存的节点
cur = temp;
}
return newHead;
}
}
16. 合并两个链表
//非递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
//归并算法
if(list1==null){
return list2;
}else if(list2 == null){
return list1;
}
//新建一个头节点
ListNode head = new ListNode(-1);
head.next = null;
//root来保存head,因为head会改变,但是第一次时会从list1和list2总选择一个头节点,这前的head没改过也就是这时候root.next=head.next
//后面head会改变
ListNode root = head;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
//接上当前这个较小的节点
head.next = list1;
//并且让head再指向这个节点,其实这里head就是指向以排序链表的最后一个节点,方便后序的节点连接上
head = list1;
list1 = list1.next;
}else{
head.next = list2;
head=list2;
list2 = list2.next;
}
}
if(list1==null){
head.next = list2;
}else{
head.next = list1;
}
return root.next;
}
}
//递归做法
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
if(list1.val <= list2.val){
list1.next = Merge(list1.next, list2);
return list1;
}else{
list2.next = Merge(list1, list2.next);
return list2;
}
}
17. 树的子结构
子结构可以是A树的任意一部分,并不一定要严格是他两边叶节点的那种结构
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//判断时候一个为null
if(root1==null || root2==null) return false;
//先判断是否是同一个树,然后判断是否跟左子树一样或者跟右子树一样
return isSameTree(root1,root2) || HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
}
//同样定义递归函数判断两个树是否相等
public static boolean isSameTree(TreeNode a,TreeNode b){
//这里为甚么是true;
//当B树的子树为空时,不管A树有没有子树直接返回true,因为到此时为止,之前的比较都是依次数值相同的。
//当B树不为空,而A树已经为空的情况下,直接返回false,很明显B树不可能为A的子结构了。
if(b==null) return true;
if(a==null) return false;
if(a.val == b.val){
return isSameTree(a.left,b.left)&&isSameTree(a.right,b.right);
}
return false;
}
}
18. 二叉树的镜像
//递归解法
public class Solution {
public void Mirror(TreeNode root) {
//先判断给的树是否为空
if(root == null) return ;
//当左右子树都为空时返回
if(root.left==null && root.right==null) return;
//这里不用判断是否全都不为空,因为只要有空跟空交换可以,即左为空右不为空 交换为左不为空右为空
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
//如果左子树存在递归镜像左子树
if(root.left!=null){
Mirror(root.left);
}
//右子树不为空递归镜像右子树
if(root.right!=null){
Mirror(root.right);
}
}
}
//用栈来实现
import java.util.Deque;
import java.util.ArrayDeque;
public class Solution {
public void Mirror(TreeNode root) {
//先判断给的树是否为空
if(root == null) return ;
//建栈
Deque<TreeNode> deque = new ArrayDeque<>();
deque.push(root);
while(!deque.isEmpty()){
TreeNode node = deque.pop();
if(node.left!= null || node.right!=null){
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
}
if(node.left!=null){
deque.push(node.left);
}
if(node.right!=null){
deque.push(node.right);
}
}
}
}
//实际中递归使用数组创建树
public TreeNode createBinaryTreeByArray(int[] array, int index) {
TreeNode tn = null;
if (index < array.length) {
int value = array[index];
tn = new TreeNode(value);
tn.left = createBinaryTreeByArray(array, 2 * index + 1);
tn.right = createBinaryTreeByArray(array, 2 * index + 2);
return tn;
}
return tn;
}
19. 顺时针打印矩阵
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> result = new ArrayList<Integer>();
if(matrix == null){
return result;
}
//获取行数和列数,如果行数为0则直接返回
int m = matrix.length;
int n = 0;
if(m > 0){
n = matrix[0].length;
}else{
return result;
}
//从(0,0)到(start,start),start = row/2>col/2?col/2:row/2
int start = 0;
while(n > start * 2 && m > start * 2){
printMatrixInCircle(matrix, m, n, start, result);
start++;
}
return result;
}
public static void printMatrixInCircle(int[][] matrix, int rows, int columns, int start, ArrayList<Integer> print){
int endRow = rows - 1 - start;//终止行
int endCol = columns - 1 - start;//终止列
//从左到右打印一行,第一步是必须的
for(int i = start; i <= endCol; i++){
int num = matrix[start][i];
print.add(num);
}
//从上到下打印一列,至少有两行才会从上到下打印,则开始行<终止行
if(start < endRow){
for(int i = start + 1; i <= endRow; i++){
int num = matrix[i][endCol];
print.add(num);
}
}
//从右到左打印一行,至少有两行两列才会从右往左打印,则开始行<终止行 && 开始列<终止列
if(start < endCol && start < endRow){
for(int i = endCol - 1; i >= start; i--){
int num = matrix[endRow][i];
print.add(num);
}
}
//从下到上打印一列,至少有两行三列才会从下往上打印,开始行<终止行 && 开始列<终止列-1
if(start < endCol && start < endRow - 1){
for(int i = endRow - 1; i >= start+1; i--){
int num = matrix[i][start];
print.add(num);
}
}
}
}
//简洁版
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] array) {
ArrayList<Integer> result = new ArrayList<Integer> ();
if(array.length==0) return result;
int n = array.length,m = array[0].length;
if(m==0) return result;
int layers = (Math.min(n,m)-1)/2+1;//这个是层数
for(int i=0;i<layers;i++){
for(int k = i;k<m-i;k++) result.add(array[i][k]);//左至右
for(int j=i+1;j<n-i;j++) result.add(array[j][m-i-1]);//右上至右下
for(int k=m-i-2;(k>=i)&&(n-i-1!=i);k--) result.add(array[n-i-1][k]);//右至左
for(int j=n-i-2;(j>i)&&(m-i-1!=i);j--) result.add(array[j][i]);//左下至左上
}
return result;
}
}
20. 包含min的栈
import java.util.Stack;
public class Solution {
//正常栈用来记录压入和弹出的值
Stack<Integer> stack1 = new Stack<>();
//借助辅助栈每次栈顶都是保存当前栈最小元素,并且弹出时辅助栈也要弹出
Stack<Integer> stack2 = new Stack<>();
public void push(int node) {
stack1.push(node);
//如果当前节点比辅助栈的栈顶元素小则压入当前元素,否则接着压入辅助栈栈顶的元素
if(stack2.isEmpty()){
stack2.push(node);
}else{
stack2.push(node<stack2.peek()?node:stack2.peek());
}
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int min() {
return stack2.peek();
}
}
21. 栈的压入、弹出序列
//借助辅助栈来实现
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
//长度不一致直接返回false
if(pushA.length != popA.length) return false;
//借助辅助栈,将压入序列压入栈中
Stack<Integer> stack = new Stack<>();
//记录弹出序列的位置,从左往右
int popIndex = 0;
//将压入序列压入栈中的过程中判断压入元素是否跟弹出序列当前位置相等
for(int i = 0;i<pushA.length;i++){
//当前元素压入栈
stack.push(pushA[i]);
//栈不为空且辅助栈顶元素跟弹出序列当前位置相等
while(!stack.isEmpty()&&stack.peek()==popA[popIndex]){
//弹出当前元素
stack.pop();
//接着比较弹出序列下一个位置和新的栈顶元素是否相等
popIndex++;
}
}
//最后看辅助栈是否为空
return stack.isEmpty();
}
}
22. 从右到左打印二叉树
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
//从上往下打印二叉树和将二叉树打印成多行不一样
//打印出多行需要记录每行有多少个元素
//1. 使用标记来记录最右的节点
//2. 在入队的时候记录每层入队的元素个数
//3. 用两个队来实现
//从上往下直接打印则只需用一个队实现即可,不需要记录每行
Queue<TreeNode> queue = new LinkedList<>();
if(root == null) return list;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode temp = queue.poll();
list.add(temp.val);
if(temp.left != null){
queue.offer(temp.left);
}
if(temp.right != null){
queue.offer(temp.right);
}
}
return list;
}
}
23. 二叉搜索树的后序遍历序列
//确定根节点,然后找到左右子树分界点(即第一个右子树节点在数组的索引)
//并判断右子树元素是否全大于根节点,是则递归判断左右子树,不是则直接返回false
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence.length == 0 || sequence==null) return false;
return verifySeq(sequence,0,sequence.length-1);
}
public static boolean verifySeq(int seq[],int first,int last){
if(last-first<=1) return true;
int root = seq[last];
int currentIndex = first;
while(currentIndex<last && seq[currentIndex]<=root)
currentIndex++;
//判断右子树是否有小于根节点的
for(int i = currentIndex;i<=last;i++){
if(seq[i] < root){
return false;
}
}
//递归判断左子树和右子树
return verifySeq(seq,first,currentIndex-1)&&verifySeq(seq,currentIndex,last-1);
}
}
24. 二叉树中和为某一值的路径
//超级简洁版
public class Solution {
public ArrayList<ArrayList<Integer>> result = new ArrayList<>();
public ArrayList<Integer> list = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
//当前节点为null直接范湖
if(root == null) return result;
list.add(root.val);
target -= root.val;
//判断是否到叶子节点了,并target是否为0如果是则将当前的list加入到result中
if(root.left==null&root.right== null&&target == 0){
//这里不能直接用list,因为这里result执行list引用的ArrayList,
//由于后序会改变list指向的List所以会改变前面保存的结果
result.add(new ArrayList<Integer>(list));
}
//递归左右子树,这里到叶节点会直接从这两行返回执行回溯
FindPath(root.left,target);
FindPath(root.right,target);
//同样回溯
list.remove(list.size()-1);
return result;
}
}
//第一思路版本
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
ArrayList<ArrayList<Integer>> arr=new ArrayList<ArrayList<Integer>>();
if(root==null)
return arr;
ArrayList<Integer> a1=new ArrayList<Integer>();
int sum=0;
pa(root,target,arr,a1,sum);
return arr;
}
public void pa(TreeNode root,int target,ArrayList<ArrayList<Integer>> arr, ArrayList<Integer> a1,int sum){
if(root==null)
return ;
sum+=root.val;
if(root.left==null&&root.right==null){
if(sum==target){
a1.add(root.val);
arr.add(new ArrayList<Integer>(a1));
a1.remove(a1.size()-1);
}
return ;
}
a1.add(root.val);
pa(root.left,target,arr,a1,sum);
pa(root.right,target,arr,a1,sum);
a1.remove(a1.size()-1);
}
}
25. 复杂链表的复制
import java.util.HashMap;
public class Solution {
public RandomListNode Clone(RandomListNode pHead){
//两种方法
//1.用hashmap来做,当我们新建节点的时候,旧节点的random指针我们可能还没有访问过
//所以就无法直接根据旧节点random指针确定新节点对应random指向的节点(当然这个节点也是根据旧节点复制的)
//用hashmap来存储新节点和旧节点,旧节点为key新节点为value,这样设计是为了根据旧节点random找到对应的旧节点然后由对应key取新节点
//首先不管random指针直接根据next复制所有节点,然后根据hashmap的key-value来确定random指向哪即可
if(pHead == null) return null;
HashMap<RandomListNode,RandomListNode> map = new HashMap<>();
//先遍历链表复制,不管random指针,然后map记录新节点和旧节点对应关系
RandomListNode newHead = new RandomListNode(pHead.label);
map.put(pHead,newHead);
//创建节点指向新的头结点,对头节点直接操作后序找不到头结点
RandomListNode pre = newHead;
RandomListNode oldNode = pHead.next;
while(oldNode != null){
RandomListNode newNode = new RandomListNode(oldNode.label);
map.put(oldNode,newNode);
pre.next = newNode;
pre = newNode;
oldNode = oldNode.next;
}
//遍历旧链表拿到random指向的旧节点然后根据map的到新节点
//同样新建节点指向newHead 免得后序返回找不到newHead位置
oldNode = pHead;
RandomListNode copy = newHead;
while(oldNode!=null){
copy.random = map.get(oldNode.random);
copy = copy.next;
oldNode = oldNode.next;
}
return newHead;
}
}
//其他方法
import java.util.HashMap;
public class Solution {
public RandomListNode Clone(RandomListNode pHead){
//两种方法
//2.1 新加入节点插入到原节点的后面
//2.2 设置random指针得时候只需要pHead.next.random = p.random.next;就可以
//2.3 拆开链表
if(pHead == null) return null;
RandomListNode pre = pHead;
while(pre!=null){
RandomListNode newNode = new RandomListNode(pre.label);
newNode.next = pre.next;
pre.next = newNode;
pre = newNode.next;
}
//遍历链表设置random指针.复用pre指针
pre = pHead;
while(pre!=null){
//判断random是否为null
if(pre.random!=null){
pre.next.random = pre.random.next;
}
pre = pre.next.next;
}
//拆分两个链表(注意细节),并再次复用pre指针
RandomListNode newHead = pHead.next;
pre = pHead;
while(pre!=null){
RandomListNode temp = pre.next;
pre.next = temp.next;
if(temp.next!=null)
temp.next = temp.next.next;
//理解这里
pre = pre.next;
}
return newHead;
}
}
26. 二叉搜索树与双向链表
//非递归(用栈来实现)
import java.util.Deque;
import java.util.ArrayDeque;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
//中序遍历刚好就是非减的序列
//1. 中序遍历用栈来实现
//2. 用递归来实现
if(pRootOfTree == null) return null;
TreeNode node = pRootOfTree;
Deque<TreeNode> stack = new ArrayDeque<>();
//pre来保存上一个节点,root来保存根节点
TreeNode root = null;
TreeNode pre = null;
//使用标记来记录链表头结点
boolean isHead = true;
while(node!=null || !stack.isEmpty()){
//到最左下
while(node!=null){
stack.push(node);
node = node.left;
}
node = stack.pop();
if(isHead){
root = node;
pre = root;
isHead = false;
}else{
pre.right = node;
node.left = pre;
pre = node;
}
node = node.right;
}
return root;
}
}
//递归做法
public class Solution {
public TreeNode Convert(TreeNode root) {
//中序遍历刚好就是非减的序列
//2. 用递归来实现
if(root == null) return null;
//都为空返回root
if(root.left==null&&root.right==null){
return root;
}
//将左子树构造成双链表并返回头节点
TreeNode left = Convert(root.left);
//操作left
TreeNode p = left;
//遍历到最后一个节点
while(p!=null&&p.right!=null){
p = p.right;
}
//左子树不为空 将根节点加到left后面
if(left!=null){
p.right = root;
root.left = p;
}
//将右子树构造成双链表
TreeNode right = Convert(root.right);
//右子树不为空则直接连接到链表上
if(right!=null){
root.right = right;
right.left = root;
}
//左子树为空直接返回root,否则返回left
//因为即使左子树为空同样还是要对右子树进行链接然后接到root上的
return left==null?root:left;
}
}
//直接用中序遍历
public class Solution {
TreeNode head = null;
TreeNode realHead = null;
public TreeNode Convert(TreeNode pRootOfTree) {
ConvertSub(pRootOfTree);
return realHead;
}
private void ConvertSub(TreeNode pRootOfTree) {
//这里面其实就是中序遍历的本质
if(pRootOfTree==null) return;
ConvertSub(pRootOfTree.left);
if (head == null) {
head = pRootOfTree;
realHead = pRootOfTree;
} else {
head.right = pRootOfTree;
pRootOfTree.left = head;
head = pRootOfTree;
}
ConvertSub(pRootOfTree.right);
}
}
27. 字符串的排序
//递归的做法
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> result = new ArrayList<>();
//按字典需打印,先将输入字符进行排序
//转成字符数组方便操作
char[] ch = str.toCharArray();
Arrays.sort(ch);
permutation(result,0,ch);
//为甚么这样下来还是有顺序不正确的???还得再按字典序排一下序
Collections.sort(result);
return result;
}
public static void permutation(ArrayList<String> result, int index,char[] ch){
if(index == ch.length-1){
result.add(new String(ch));
}
for(int i = index;i<ch.length;i++){
if(index !=i&&ch[index]==ch[i]){
continue;
//交换index和i的位置
}
swap(ch,index,i);
permutation(result,index+1,ch);
//得到结果之后换回来
swap(ch,index,i);
}
}
public static void swap(char[] ch,int a,int b){
char temp = ch[a];
ch[a] = ch[b];
ch[b] = temp;
}
}
//非递归的做法:next_permutation()的实现
28. 数组中出现超过一半的数字
//如果超过一半则肯定是真中间那个元素,所以直接统计中间那个元素的个数是否超过一半
import java.util.Arrays;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
//遍历元素统计个数最多的元素然后跟长度比较
if(array==null&&array.length==0) return 0;
//超过半数肯定排序后中年元素就是该元素,然后左右遍历统计相同的长度然后比较
Arrays.sort(array);
int count = 1,mid = array.length/2;
//从左往右统计
for(int i = mid+1;i<array.length;i++){
if(array[i] == array[mid])
count++;
}
//从右往左统计
for(int i = mid-1;i>=0;i--){
if(array[i] == array[mid])
count++;
}
return count>mid?array[mid]:0;
}
}
29. 最小的k个数
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
if(input==null||k>input.length||k<=0){
return result;
}
//1. 部分排序思想
//2. 最傻逼办法,先完全排序然后直接取前k个,如果数据很多那排序要很长时间
//(1)冒泡先冒k个最小的
//(2)partation思想,判断中轴的索引pos是否为k-1,如果是则直接返回前k个元素,如果pos>k,则接着对左半部分partation
//如果pos<k,接着对右半部分partation
//(3)堆排序,定义k个大小的小根堆,然后添加所有元素维护堆,最后返回的堆肯定就是最小的k个元素
//默认是小根堆,这里还要转成大根堆
PriorityQueue<Integer> maxHeap = new PriorityQueue(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
for (int i = 0; i < input.length; i++) {
if (maxHeap.size() != k) {
maxHeap.offer(input[i]);
//堆顶元素最大,如果后序元素比堆顶还大那肯定位置k,如果小于堆顶则替换掉堆顶
} else if (maxHeap.peek() > input[i]) {
Integer temp = maxHeap.poll();
temp = null;
maxHeap.offer(input[i]);
}
}
for (Integer integer : maxHeap) {
result.add(integer);
}
return result;
}
}
//采用partation思想:partation思想,判断中轴的索引pos是否为k-1,如果是则直接返回前k个元素,
//如果pos>k,则接着对左半部分partation,如果pos<k,接着对右半部分partation
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> output = new ArrayList<>();
if(input.length == 0 || k<= 0 || k>input.length)
return output;
int start = 0;
int end = input.length-1;
int index = partition(input,start,end);
while(index != k-1){
if(index > k-1){
end = index -1;
index = partition(input,start ,end);
}
else{
start = index+1;
index = partition(input,start ,end);
}
}
for(int i = 0;i<k;i++){
output.add(input[i]);
}
return output;
}
public int partition(int[] arr, int left, int right) {
int result = arr[left];
if (left > right)
return -1;
while (left < right) {
while (left < right && arr[right] >= result) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] < result) {
left++;
}
arr[right] = arr[left];
}
arr[left] = result;
return left;
}
}
30. 连续子数组的最大和
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
//连续子数组的最大和
if(array.length == 0) return 0;
int tempMax = array[0];
//Max可能一开始会很小
int Max = Integer.MIN_VALUE;
for(int i =1;i<array.length;i++){
//当前最大和与当前元素相加的值是否比当前元素大
if(tempMax + array[i]> array[i]){
tempMax = tempMax + array[i];
}else{
tempMax = array[i];
}
if(tempMax > Max){
Max = tempMax;
}
}
return Max;
}
}
31. 整数中1出现的次数
//最简单方法,直接将数组转成字符在统计
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
//最简单方法把数组换成字符然后统计
int count = 0;
while(n>0){
String str = String.valueOf(n);
char[] ch = str.toCharArray();
for(int i=0;i<ch.length;i++){
if(ch[i] == '1'){
count++;
}
}
n--;
}
return count;
}
}
//查找规律,直接根据规律得出结果
/*
1)此位大于1,这一位上1的个数有([n/10^(b+1]+1)*10^b
2)此位等于0,为([n/10^(b+1)])*10^b
3)此位等于1,在0的基础上加上n mod 10^b + 1
举个例子:30143
由于3>1,则个位上出现1的次数为(3014+1)*1
由于4>1,则十位上出现1的次数为(301+1)*10
由于1=1,则百位上出现1次数为(30+0)*100+(43+1)
由于0<1,则千位上出现1次数为(3+0)*1000
*/
public class Solution {
public int NumberOf1Between1AndN_Solution(int num) {
//统计规律直接根据规律来算
if(num <= 0) return 0;
//统计1的个数
int count = 0;
//当前位的值
int current = 0;
//当前位后面剩余的值
int remain = 0;
//基数
int base = 1;
while(num>0) {
//当前位
current = num%10;
//统计当前位左边所有位可能的值,比如3012,当前位为2,则num=302
num = num/10;
//根据当前位的值确定当前位可能出现1的次数
if(current > 1) {
count += (num+1)*base;
}else if(current==1) {
count += (num*base + remain + 1);
}else {
count += num*base;
}
//下一位的求解可能需要用到不完整的部分值
remain += current*base;
base *= 10;
}
return count;
}
}
32. 把数组排成最小的数
//定义比较器比较两个数组成字符串的字典序排序
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
public String PrintMinNumber(int [] arr) {
//相加根据字典序排序
//if(arr == null||arr.length==0)return "";
//整数数组换成字符数组
String[] strArr = new String[arr.length];
for(int i =0;i<arr.length;i++){
strArr[i] = String.valueOf(arr[i]);
}
//直接根据Arrays.sort添加自定义的比较器来对数组进行排序
Arrays.sort(strArr,new Comparator<String>(){
@Override
public int compare(String s1, String s2) {
String left = s1+s2;
String right = s2+s1;
//比较是s1s2还是s2s1大
/*
* 如果s1s2>s2s1,打印s1s2,即s2排在s1右边,则对应数组中应该认为s1<s2;
* compare根据比较结果正负判断大小,结果为正s1>s2即s1应该在右边,结果为负s2>s1即s2应该在右边;
* 所以这里加上'-'号就是求组合后最大的数了!!!
*/
return left.compareTo(right);
}
});
//拼接排好序的字符串数组即可
StringBuilder sb = new StringBuilder();
for(String str:strArr){
sb.append(str);
}
return sb.toString();
}
}
33. 丑数
//暴力法
/*
丑数只能被2,3,5整除。也就是说如果一个数能被2整除,我们把它连续除以2;
如果能被3整除,就连续除以3;如果能被5整除,就除以5.如果最后我们得到的是1,
那么这个数就是丑数,否则不是。接下来,我们只需要按照顺序判断每个整数是不是丑数,
*/
//优化方法
/*
在乘以2,3,5的时候,也能得到若干个小于或等于M的结果,由于是按照顺序生成的,
小于或者等于M肯定已经在数组中了,我们不需要再次考虑;比如对乘以2而言,肯定存在某一个丑数T2,
排在它之前的每一个丑数乘以2得到的结果都会小于已有的最大丑数,在它之后的每一个丑数乘以2得到的结果都会太大。
我们只需记下这个丑数的位置,同时每次生成新的丑数的时候,去更新这个T2位置,
下次直接从这个位置开始乘以2即可,对乘以3和5而言,也存在这同样的T3和T5。
*/
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index<=6) return index;
int count = 1;
int[] dp = new int[index];
int i2=0,i3=0,i5=0;
dp[0]=1;
while(count<index){
int n2 = dp[i2]*2;
int n3 = dp[i3]*3;
int n5 = dp[i5]*5;
int min = Math.min(n2, Math.min(n3, n5));
dp[count++] = min;
if (min == n2) i2++;
if (min == n3) i3++;
if (min == n5) i5++;
}
return dp[index-1];
}
}
34. 数组中第一个只出现一次的字符
public class Solution{
public int FirstNotRepeatingChar(String str) {
//使用整型数组来做
//hashmap来做,key是字符,值是字符出现的次数
//将全部字符放入hashmap中返回第一个
if(str == null || str.length() == 0) return -1;
//先将字符串转成字符数组
char[] ch = str.toCharArray();
HashMap<Character,Integer> map = new HashMap<>();
for(int i =0;i<ch.length;i++){
if(map.containsKey(ch[i])){
int time = map.get(ch[i])+1;
map.put(ch[i],time);
}else{
map.put(ch[i],1);
}
}
//遍历数组寻找第一个值为1的key
for(int i = 0;i<ch.length;i++){
if(map.get(ch[i]) == 1){
return i;
}
}
//返回的是索引的位置,如果比遍历完都没有则返回-1
return -1;
}
}
//直接用数组来做也是一样
public int FirstNotRepeatingChar(String str){
char[] c = str.toCharArray();
int[] a = new int['z'];
for (char d : c)
a[(int) d]++;
for (int i = 0; i < c.length; i++)
if (a[(int) c[i]] == 1)
return i;
return -1;
}
//最省空间的做法是用两个bit数组:
//00表示0 01表示1 非00和01表示大于1的数
public int FirstNotRepeatingChar(String str) {
BitSet bs1 = new BitSet(256);
BitSet bs2 = new BitSet(256);
for (char c : str.toCharArray()) {
if (!bs1.get(c) && !bs2.get(c))
bs1.set(c); // 0 0
else if (bs1.get(c) && !bs2.get(c))
bs2.set(c); // 0 1
}
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (bs1.get(c) && !bs2.get(c))
return i;
}
return -1;
}
35. 数组中的逆序对
/*巧妙利用归并排序的思想,在归并排序上改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
*/
public class Solution {
//定义成员变量count和temp数组
public long count = 0;
public int[] temp ;
public int InversePairs(int [] array) {
temp = new int[array.length];
mergeArray(array,0,array.length-1);
return (int) (count % 1000000007);
}
private void mergeArray(int[] array, int left, int right) {
if(right-left<1) return;
int mid = left + (right-left)/2;
mergeArray(array,left,mid);
mergeArray(array,mid+1,right);
merge(array,left,mid,right);
}
private void merge(int[] array, int left, int mid, int right) {
int i = left,j = mid+1,k = left;
while(i<=mid||j<=right){
if(i>mid)
temp[k] = array[j++];
else if(j>right){
temp[k] = array[i++];
}else if(array[i]<array[j]){
temp[k] = array[i++];
}else{
//当左边大于右边时
temp[k] = array[j++];
//则array[i]到array[mid]都是大于array[j]
this.count += mid-i+1;
}
k++;
}
for(k = left;k<=right;k++){
//更新到原数组中去
array[k] = temp[k];
}
}
}
36. 两个链表的第一个公共节点
public class Solution36 {
public class ListNode{
private int val;
private ListNode next;
public ListNode(int val){
this.val = val;
}
}
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//1. 根据长度计算出两数组的长度差k, 然后让长度长的先走k步,然后两者一起走,指向相同的节点时即为第一个相同节点啊
//2. 栈来做
/*3. 最佳做法:
设 A 的长度为 a + c, B 的长度为 b + c, 其中 c 为尾部公共部分长度, 可知 a + c + b = b + c + a。
当访问 A 链表的指针访问到链表尾部时, 令它从链表 B 的头部重新开始访问链表 B; 同样地, 当
访问 B 链表的指针访问到链表尾部时, 令它从链表 A 的头部重新开始访问链表 A。 这样就能控制
访问 A 和 B 两个链表的指针能同时访问到交点。
*/
ListNode l1 = pHead1,l2 = pHead2;
while (l1!=l2){
l1 = l1!=null?l1.next:l2;
l2 = l2!=null?l2.next:l1;
}
return l1;
}
}
37. 统计数字在排序数组出现的次数
//用二分法分别确定左右的位置,特别注意while判断条件和mid的设置
public class Solution37{
public int GetNumberOfK(int[] nums, int K) {
int first = getFirstK(nums, K);
int last = getLastK(nums, K);
return first == -1 || last == -1 ? 0 : last - first + 1;
}
//找到左边界
private int getFirstK(int[] nums, int K) {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (nums[m] >= K) h = m - 1;
else l = m + 1;
}
if (l > nums.length - 1 || nums[l] != K) return -1;
return l;
}
//找到右边界
private int getLastK(int[] nums, int K) {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (nums[m] > K) h = m - 1;
else l = m + 1;
}
if (h < 0 || nums[h] != K) return -1;
return h;
}
}
38. 二叉树的深度
//简单的递归做法
public class Solution {
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
return 1+Math.max(TreeDepth(root.left),TreeDepth(root.right));
}
}
39. 二叉平衡树
//递归应用,直接用成员变量记录是否有左右子树高度相差超过1
public class Solution {
private boolean isBalanced = true;
public boolean IsBalanced_Solution(TreeNode root) {
height(root);
return isBalanced;
}
private int height(TreeNode root) {
if(root == null) return 0;
int left = height(root.left);
int right = height(root.right);
if(Math.abs(left-right)>1) isBalanced = false;
return 1+ Math.max(left,right);
}
}
40. 数组中只出现一次的数字(只有两个数字只出现过一次,其他都出现两次)
//一定要注意有else和没else的区别:
// 有else时if条件满足则只会执行if体的语句而不会之心else体语句
// 没else时不管if条件满不满足都会执行else体的语句
public class Solution40 {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
//直接用位运算异或来做
/*
两个不相等的元素在位级表示上必定会有一位存在不同。
将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。
diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。
*/
int diff = 0;
for(int num:array){
//所有元素异或后的结果为不重复两个元素的异或结果
diff ^= num;
}
//得到最右侧不为0的位,除了这一位为1其他全为0
diff &= -diff;
//再次遍历数组让diff跟所有元素的异或即可得到两个不重复的值
for(int num:array){
//这些重复的元素可以分成两拨:一波跟其中一个不重复在这位上相同,一波跟另一个不重复在这位上不同
if((diff&num) == 0) num1[0] ^= num;
else num2[0] ^= num;
}
}
}
41. 和为s的连续正数序列
//
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
int big = 2;
int small = 1;
int curSum = 3;
while(small<(1+sum)/2&&big<sum){
if(curSum == sum){
ArrayList<Integer> list = new ArrayList<>();
for(int i = small;i<=big;i++){
list.add(i);
}
result.add(list);
curSum -= small;
small++;
big++;
curSum += big;
}else if(curSum<sum){
big++;
curSum += big;
}else{
curSum -= small;
small++;
}
}
return result;
}
}
42. 和为s的两个数字
/*
同样用两个指针small和big
初始small指向index:0 big指向index:array.length-1
如果sum大了big--,sum小了small++;
为什么small和big越远乘积越小???
当两个数的和a一定时,设两个数为x,a-x
y=x(a-x)=-x^2+ax 是一个二次函数
当x=-a/2时有最大值
当x离-a/2越来越远时,y越来越小
当两个数的积a一定时,设这两个数为x,a/x
y=x+a/x
y'=1-a/(x^2) 当x=√a,y'=0
函数先递减后递增
当两个数相差越大,y越来越大
*/
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
int i = 0, j = array.length - 1;
while (i < j) {
int cur = array[i] + array[j];
//乘积最小的其实就是第一个找到的两个数
if (cur == sum) return new ArrayList<Integer>(Arrays.asList(array[i], array[j]));
else if (cur < sum) i++;
else j--;
}
return new ArrayList<Integer>();
}
}
43. 左旋转字符串
//这种左旋转直接加上原来的字符串然后截原来的长度即可
public class Solution {
public String LeftRotateString(String str,int n) {
if(str == null || n<0 || n>str.length()){
return new String();
}
//直接先加上原来的str然后从n开始截原来长度的字符串即可
int len = str.length();
return str.concat(str).substring(n,n+len);
}
}
44. 反转单词顺序表
//必须注意全是空格的情况!!!
public class Solution {
public String ReverseSentence(String str) {
//str为null
if(str == null) return null;
//str只有空格时,这里必须搞清楚,因为可能全是空格,这是后不需要进行反转!!!!
if(str.trim().equals("")) return str;
//先反转每个单词然后反转整个句子或者先反转整个句子然后反正每个单词
StringBuilder result = new StringBuilder();
//根据空格将str字符串数组
String[] strArr = str.trim().split(" ");
for(int i = strArr.length-1;i>=0;i--) {
//最后一个不用加" "
if(i == 0) {
result.append(strArr[i]);
}else {
result.append(strArr[i]).append(" ");
}
}
return result.toString();
}
}
45. 扑克牌顺子
//排序做法
import java.util.Arrays;
public class Solution45 {
public boolean isContinuous(int [] numbers) {
//判断输入的数组是不是可以组成顺子
//判断0的个数(最多有4个),然后计算非0元素之间的距离(相差大于1才开始算距离),如果小于等于0的个数则可以组成,否则不能组成
//排序确定0的个数
if(numbers == null || numbers.length!=5) return false;
Arrays.sort(numbers);
int countOf0 = 0,gap = 0;
int i = 0;
for(;i<4;i++){
if(numbers[i] == 0){
countOf0++;
}else if (numbers[i+1] - numbers[i] == 0){
//有相同的牌直接返回false
return false;
}else{
gap += numbers[i+1] - numbers[i] - 1;
}
}
return countOf0>=gap?true:false;
}
}
//当然也可以根据牌的最大最小值来判断,这样不用排序
//但是要满足条件:(1)max - min <5 (2)除0外没有重复的数字(牌) (3)数组长度 为5*/
46. 孩子们的游戏(约瑟夫环)
//方法1:用数组来模拟环,将被叫到元素的值设置为-1来模拟以被删除
public class Solution46 {
public int LastRemaining_Solution(int n, int m) {
//判断m或者n是否小于1
if(n<1||m<1) return -1;
//step记录是否走了m步了
int step = 0;
//count记录是否已经喊了n次
int count = n;
//arr用来模拟,用index来表示数组的下标
int[] arr = new int[n];
int index = -1;
while(count>0){
index++;
//如果到了数组尾则回到头,模拟环
if(index >= n) index = 0;
//判断是否为-1,-1的点说明已经被删掉了直接跳过
if(arr[index] == -1) continue;
//步数+1
step++;
//数到m了则当前元素赋值为-1,代表删除
if(step == m){
arr[index] = -1;
step = 0;
count-- ;
}
}
return index;
}
}
//方法2:用归纳法来统计规律
public class Solution {
public int LastRemaining_Solution(int n, int m) {
//直接递推式来做
if(n<1||m<1) return -1;
int p=0;
for(int i = 2;i<=n;i++){
p = (p+m)%i;
}
return p;
}
}
47. 求1+2+3+...+n
public class Solution47 {
public int Sum_Solution2(int n) {
//位运算
//1+2+3+...+n=(n+1)*n/2=n^2/2+n/2直接用平方和位运算来除2
int sum = (int)(Math.pow(n,2) + n );
return sum >> 1;
}
//用递归的方式做
//1.需利用逻辑与的短路特性实现递归终止。
//2.当n==0时,(n>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0;
//3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。
public int Sum_Solution(int n){
int sum = n;
boolean ans = (n>0)&&((sum += Sum_Solution(n-1))>0);
return sum;
}
}
48. 不用加减乘除做加法
/*
5的二进制是101,17的二进制是10001,还是试着把计算分成三步:
第一步各位相加但不计进位,得到的结果是10100;
第二步记下进位。在这个例子中只在最后一位相加时产生进位,结果是二进制的10;
第三步把前两步的结果相加,得到的结果是10110,转换成十进制刚好是22,由此可见三步走的策略对二进制也是适用的。
接下来把二进制的加法用位运算来替换:
第一步不考虑进位对每一位相加。0+0,1+1的结果都是0,1+0的结果是1,0+1的结果是1,我们注意到这和异或的结果是一样的。这一步可以用m^n实现。
第二步加上进位,进位如何来,用m&n可以得到m和n中都为1的bit位,而不全为1的位则全部变为了0,该位相加会发生进位,
使得左边一位加1,因此(m&n)<<1可得到进位后要加的1的位置;
第三步将前面两步的结果相加,这里相加同样还是用异或运算来实现。相加的时候还有可能再产生进位,因此二者相加的过程可以再次重复循环步骤1和2,
直到(m&n)<<1 == 0,这时候不会再产生进位,退出循环即可。
*/
public class Solution48 {
public int Add(int num1,int num2){
//不含进位的和
int sum;
//进位
int add1;
//复用num1和num2,这里判断必须是n!=0而不是n>0,因为参与的两个数可能有负数
while(num2 != 0){
sum = num1^num2;
//推出循环条件是不在产生进位
add1 = (num1&num2) << 1;
num1 = sum;
num2 = add1;
}
return num1;
}
}
49. 把字符串转成整数
public class Solution {
public int StrToInt(String str) {
if(str == null || str.trim().equals("")) return 0;
char[] ch = str.toCharArray();
int sum = 0;
//如果有负号从1开始遍历,没有从0开始
int first = 0;
//判断是否有符号‘-’
if(ch[0] == '-'){
first = 1;
}
//判断符号后面是否有非数组的字符
for(int i = first;i<ch.length;i++){
if(ch[i] == '+')
continue;
if(ch[i]<='9'&&ch[i]>='0'){
sum = sum*10 + ch[i] - 48;
}else{
return 0;
}
}
return first == 1?(-1)*sum:sum;
}
}
50. 数组中重复的数
//直接利用String的lastIndexOf和indexOf
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(numbers == null || length<2) return false;
String str = Arrays.toString(numbers);
for(int i = 0;i<length;i++){
if(str.indexOf(numbers[i]+"") != str.lastIndexOf(numbers[i]+"")){
duplication[0] = numbers[i];
return true;
}
}
return false;
}
//不许要额外数组来实现,因为长度为n的数组且数字范围都是在0到n-1
/*
利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + length,
之后再遇到相同的数时,会发现对应位上的数已经大于等于length了,那么直接返回这个数即可
*/
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(numbers == null || length<2) return false;
for ( int i= 0 ; i<length; i++) {
int index = numbers[i];
if (index >= length) {
index -= length;
}
if (numbers[index] >= length) {
duplication[0] = numbers[i]-length;
return true;
}
numbers[index] = numbers[index] + length;
}
return false ;
}
51. 构建乘积数组
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
//先求前一部分
//然后在前一部分的基础上求后一部分
//首先B[i]=A[1]*A[2]*A[3]*...*A[i-1]
int n = A.length;
int[] B = new int[n];
//先求前一部分
B[0] = 1;
for (int i = 1; i < n; i++) {
//利用前面求过的值
B[i] = B[i - 1] * A[i - 1];
}
/*
* 然后求后一部分
* 只需乘上后面的A[i+1]*A[i+2]*...*A[n-1]
* pre保存前面求过的A[i+1]*...*A[n-1]
*/
int pre = 1;
for (int i = n - 2; i >= 0; i--) {
pre *= A[i + 1];
/*
* B[n-1]=A[1]*...*A[n-2]所以前已经求出, 这里不需要再求B[n-1]
* 相当于用上个for求的B[i]再乘上后面的一部分
*/
B[i] = B[i] * pre;
}
return B;
}
}
52. 正则表达书匹配
public class Solution52 {
public boolean match(char[] string, char[] pattern)
{
if (string == null || pattern == null) {
return false;
}
String p = new String(pattern);
String str = new String(string);
return isMatch(str,p);
}
public boolean isMatch(String str,String p){
//都为空则返回true
if(p.isEmpty())
return str.isEmpty();
//p长度为1,则s长度必须为1且满则s,p首字符相同或者是p为'.'
if(p.length() == 1)
return (str.length() == 1)&&(str.charAt(0) == p.charAt(0)||p.charAt(0) == '.');
//当p第二个字符不是'*'时
if(p.charAt(1) != '*'){
//s为空则直接返回false
if(str.isEmpty()) return false;
//判断首字符是否相同(p首字符为.或者s和p首字符相同)且都从第二个字符进行递归判断
return (str.charAt(0) == p.charAt(0)||p.charAt(0) == '.') &&
isMatch(str.substring(1),p.substring(1));
}
//当p第二个字符是'*'时
//s不为空且和s和p的首字符匹配(首字符直接相等或者p首字符为.),递归调用匹配s和去掉前两个字符的p,相当于把*看作是0
//如果匹配则直接返回true,否则将s去掉首字符跟去掉前两个字符的p比较
while(!str.isEmpty()&&(p.charAt(0) == str.charAt(0)|| p.charAt(0) == '.')){
if(isMatch(str,p.substring(2))) return true;
str = str.substring(1);
}
//递归返回s和去掉前两个字符的匹配结果
return isMatch(str,p.substring(2));
}
}
//更加简短写法
public boolean isMatch(String str,String p){
//是否都是为空
if(p.isEmpty()){
return str.isEmpty();
}
//1.当p的第二个字符为*号时,由于*号前面的字符的个数可以任意,可以为0,
//那么我们先用递归来调用为0的情况,就是直接把这两个字符去掉再比较
//2.或者当s不为空,且第一个字符和p的第一个字符相同时,我们再对去掉首字符的s和p调用递归,
// 注意p不能去掉首字符,因为*号前面的字符可以有无限个
if(p.length()>1&&p.charAt(1)=='*'){
return isMatch(str,p.substring(2)) ||
(!str.isEmpty()&&(str.charAt(0) == p.charAt(0)||p.charAt(0)=='.')&&isMatch(str.substring(1),p));
}else{
//如果第二个字符不为*号,那么我们就老老实实的比较第一个字符,然后对后面的字符串调用递归
return !str.isEmpty() && (str.charAt(0) == p.charAt(0)||p.charAt(0)=='.') &&
isMatch(str.substring(1),p.substring(1));
}
}
53. 表示数值的字符串
public class Solution53 {
//1. 正则表达式解法
public boolean isNumber(String str){
return str.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)");
}
//2. 系统转换函数解法
public boolean isNumber2(String str){
try{
double d = Double.parseDouble(str);
}catch (NumberFormatException e){
//转换出现异常直接返回false;
return false;
}
//成功转换返回true
return true;
}
//3. 硬核解法:逐个字符检查
public boolean isNumber3(String s){
char[] str = s.toCharArray();
//标记符号、小数点、e是否出现过
boolean sign=false,decimal = false,hasE = false,hasNum = false;
for(int i = 0;i<str.length;i++) {
//判断e前面是否有数字出现
if(str[i]>='0'&& str[i]<='9') {
hasNum = true;
}
/*
* 1. 判断'e'和'E'
*/
//e后面一定要接数字
if(str[i] == 'e' || str[i] == 'E') {
if(!hasNum || i == str.length -1) return false;//e后面一定要有数字
if(hasE) return false;//e只能存在一个
hasE = true;
/*
* 2. 判断'+'和'-'
*/
}else if(str[i] == '+' || str[i] == '-') {
//第二次出现+或-时,必须是紧跟在e后面
if(sign&&str[i-1] != 'e' &&str[i-1]!='E') return false;
//第一次出现+或-时,如果不是在字符串开头则一定要在e后面
if (!sign && i > 0 && str[i - 1] != 'e' && str[i - 1] != 'E') return false;
sign = true;
/*
* 3. 判断'.'
*/
}else if(str[i] == '.') {
//e后面不能有小数点
//小数点不能出现两次,当hasE=false时判断deciaml,如果前面已经有小数点即decimal=true则返回false
if(hasE || decimal) return false;
decimal = true;
/*
* 4. 判断是否为数字
*/
}else if(str[i]<'0'||str[i]>'9') {
return false;
}
}
return true;
}
}
54. 字符流中第一个不重复的字符
//1. 用int数组来做不能直接统计出现次数,因为顺序遍历时第一个值为1的ch不一定是字符流中第一个不重复的字符
//2. 也可以用LinkedHashMap来做
public class Solution54 {
//可以用256的数组记录
int[] count = new int [256];
int index = 1;
//Insert one char from stringstream
public void Insert(char ch)
{
if(count[ch] == 0)
//这里从字节流后来的字符值肯定越大
count[ch] =index++;
else
//有重复的直接只为-1
count[ch] = -1;
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
int temp = Integer.MAX_VALUE;
char ch = '#';
for(int i = 0;i<256;i++){
//找到字符流中值最小的(不能为0和-1)即为第一个不重复的字符
if(count[i]!=0&&count[i]!=-1&&count[i]<temp){
temp = count[i];
ch = (char)i;
}
}
return ch;
}
}
55. 链表中环的入口
/*
(1)存在环,所以两个指针必定相遇在环中的某个节点上。
此时 fast 移动的节点数为 x+2y+z(因为相遇时fast比slow多走一个环的长度),slow 为 x+y,
由于 fast 速度比 slow 快一倍,因此 x+2y+z=2(x+y),得到 x=z。
(2)在相遇点,slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,
并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点,由于 x=z,因此 fast 和 slow 将在环入口点相遇。
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
//判断是否包含环用快慢指针,如果有环直接快慢指针肯定会相遇的
if(pHead == null) return null;
//快指针fast 慢指针slow
ListNode fast = pHead,slow = pHead;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
//存在环时获取环的入口
if(fast == slow){
//让快指针到链表头,然后速度变成跟慢指针一样
fast = pHead;
//再相遇时就是在环的入口了
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
//显然当循环遇到null说明没有环
return null;
}
}
56. 删除链表中包含重复的所有节点
//非递归做法
public class Solution57 {
//这是去重,不是去掉有重复的所有元素
public ListNode deleteDuplication2(ListNode pHead)
{
//直接用first指针和last指针来做
//last指针指向下一个不相等的节点
ListNode first = pHead,last = first;
while(first != null && first.next!=null){
if(first.val == first.next.val){
int val = first.val;
while(first != null && first.val == val){
first = first.next;
}
last.next = first;
last = last.next;
}else
first = first.next;
last = first;
}
return pHead;
}
public ListNode deleteDuplication(ListNode pHead)
{
// 设置一个节点指向头结点
ListNode first = new ListNode(-1);
first.next = pHead;
// last总是指向已去重序列的最后一个节点
ListNode last = first;
while (pHead != null && pHead.next != null) {
if (pHead.val == pHead.next.val) {
int val = pHead.val;
//遇到重复元素一直往后移动直到找到不重复节点,然后接到last后面
while (pHead != null && pHead.val == val)
pHead = pHead.next;
last.next = pHead;
} else {
last = pHead;
pHead = pHead.next;
}
}
return first.next;
}
}
//递归做法
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null) return null;
ListNode next = pHead.next;
if (next == null) return pHead;
if (pHead.val == next.val) {
while (next != null && pHead.val == next.val) next = next.next;
return deleteDuplication(next);
}
pHead.next = deleteDuplication(pHead.next);
return pHead;
}
57. 二叉树的下一个结点
/* 1)当前结点有右子树,那么下一个结点一定在右子树中, 且右子树中最左下的那个结点就是答案
2)当前结点没有右子树,这个时候就要回溯了,向父节点回溯,
父节点为空:说明当前节点为根节点且没有右子树则就是最后中序的最后一个节点
父节点不为空:当前结点是它父亲的左孩子则直接返回其父节点
当前结点是它父亲的右孩子则说明父结点已经被访问过了,继续回溯,直到回溯结点是它父亲的左孩子为止,这时返回回溯的节点;
如果回溯到根结点还没有结束,那么说明当前结点是整个二叉树最右下的那个结点,也就是说它没有下一个结点了。
*/
public class Solution58 {
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
//指向父节点的指针
TreeLinkNode parent = null;
TreeLinkNode(int val) {
this.val = val;
}
}
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode.right!= null){
TreeLinkNode node = pNode.right;
while(node.left!=null){
node = node.left;
}
return node;
}else{
while(pNode.parent!=null){
TreeLinkNode parent = pNode.parent;
if(parent.left == pNode){
return parent;
}
pNode = pNode.parent;
}
}
return null;
}
}
58. 对称的二叉树
public class Solution58 {
public class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val){
this.val = val;
}
}
boolean isSymmetrical(TreeNode pRoot)
{
//简单的递归做法
if(pRoot == null) return true;
return isSymmetrical(pRoot.left,pRoot.right);
}
private boolean isSymmetrical(TreeNode left, TreeNode right) {
if(left == null && right == null) return true;
if(left == null || right == null ) return false;
if(left.val != right.val) return false;
return isSymmetrical(left.left,right.right)&&isSymmetrical(left.right,right.left);
}
}
59. 按之字形顺序打印二叉树
public class Solution {
//用队列来做,reverse记录是否反转的标志
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(pRoot == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot);
//记录反转标志
boolean reverse = false;
//count记录每层的宽度
int count = 0;
while(!queue.isEmpty()){
ArrayList<Integer> list = new ArrayList<>();
count = queue.size();
for(int i = 0;i<count;i++){
TreeNode node = queue.poll();
list.add(node.val);
if(node.left != null)
queue.add(node.left);
if(node.right != null)
queue.add(node.right);
}
if(reverse) Collections.reverse(list);
reverse = !reverse;
result.add(list);
}
return result;
}
}
60. 把二叉树打印成多行
public class Solution60 {
//跟之字形打印几乎一模一样,只不过不用反转
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
if (pRoot == null) return ret;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot);
while (!queue.isEmpty()) {
int cnt = queue.size();
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < cnt; i++) {
TreeNode node = queue.poll();
list.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
ret.add(list);
}
return ret;
}
}
61. 序列化二叉树
//直接对字符串进行操作
public class Solution61 {
public class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val){
this.val = val;
}
}
public String GolbalStr;
public String Serialize(TreeNode root){
if(root == null){
return "#";
}
//类似与先序遍历
return root.val+" "+ Serialize(root.left) +" "+ Serialize(root.right);
}
public TreeNode Deserialize(String str){
GolbalStr = str;
return Deserialize();
}
//利用先序恢复树
public TreeNode Deserialize(){
//树节点的值可能有几个字符
if(GolbalStr.length() == 0) return null;
int index = GolbalStr.indexOf(" ");
//当前节点的val值
String node = (index == -1?GolbalStr:GolbalStr.substring(0,index));
//剩下的字符串
GolbalStr = (index == -1?"" :GolbalStr.substring(index+1));
//是否为空节点,为空节点直接返回null
if(node.equals("#")){
return null;
}
TreeNode t = new TreeNode(Integer.valueOf(node));
//这里不用全局变量会出现问题
t.left = Deserialize();
t.right = Deserialize();
return t;
}
}
//将字符串转化成字符数组来做
public class Solution61{
public int index = -1;
String Serialize(TreeNode root) {
StringBuffer sb = new StringBuffer();
if(root == null){
sb.append("#,");
return sb.toString();
}
sb.append(root.val + ",");
sb.append(Serialize(root.left));
sb.append(Serialize(root.right));
return sb.toString();
}
TreeNode Deserialize(String str) {
index++;
int len = str.length();
if(index >= len){
return null;
}
String[] strr = str.split(",");
TreeNode node = null;
if(!strr[index].equals("#")){
node = new TreeNode(Integer.valueOf(strr[index]));
node.left = Deserialize(str);
node.right = Deserialize(str);
}
return node;
}
}
62. 二叉搜索树的第k小节点
public class Solution62 {
TreeNode result;
int index = 0;
TreeNode KthNode(TreeNode pRoot, int k) {
//二叉搜索树的中序遍历(左根右)是非递减序列
//中序遍历第k个节点就是第k小的元素
inOrder(pRoot,k);
return result;
}
private void inOrder(TreeNode pRoot, int k) {
if(pRoot == null || index >= k){
return;
}
inOrder(pRoot.left,k);
index++;
if(index == k){
result = pRoot;
}
inOrder(pRoot.right,k);
}
}
63. 数据流中的中位数
import java.util.PriorityQueue;
public class Solution63 {
/*
维护一个大顶堆,一个小顶堆,且保证两点:
(1)小顶堆里的元素全大于大顶堆里的元素,即小顶堆里维护的是数据流中后一部分较大值,大顶堆维护的是数据流中前一部分的较小值;
(2)两个堆个数的差值小于等于1,元素个数为偶数让两个堆中元素个数相同,为奇数时让小顶堆元素个数比大顶堆多1个。
则当总元素个数为奇数时,中位数就是小顶堆堆顶;当总元素个数为偶数时,中位数就是两个堆堆顶平均数。
*/
/*
在将元素依次插入两个堆中时需要注意:
(1)n为偶数时插入元素到小顶堆,因为小顶堆元素都要大于大顶堆,但是新插入的元素不一定比大顶堆元素大,
因此要先将元素插入大顶堆,然后利用大顶堆的特点,取出堆顶元素即为最大元素,此时再插入小顶堆!
(2)n为奇数时同理先插入到小顶堆中,然后取出小顶堆堆顶即最小元素插入到大顶堆中。
*/
//小顶堆保存大的一部分,大顶堆保存小的一部分
PriorityQueue<Integer> right = new PriorityQueue<>();
PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
int N = 0;
public void Insert(Integer num) {
if(N%2 ==0){
//为偶数时先插入到大顶堆中选出最大的,然后插入到小顶堆
left.add(num);
right.add(left.poll());
}else{
//为奇数时先插入到小顶堆选出右边最小的即为左边最大的然后插入到左边
right.add(num);
left.add(right.poll());
}
N++;
}
public Double GetMedian() {
//为偶数则返回两个堆顶的平均值
if(N%2 == 0){
return (left.peek()+right.peek())/2.0;
}else{
//为奇数直接返回大顶堆堆顶
return (double)right.peek();
}
}
}
64. 滑动窗口的最大值
import java.util.ArrayList;
import java.util.PriorityQueue;
//用大顶堆来维护最大元素
public class Solution64 {
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> result = new ArrayList<>();
if(size>num.length ||size<1)
return result;
PriorityQueue<Integer> heap = new PriorityQueue<>((o1, o2) -> o2-o1);
for(int i = 0;i<size;i++){
heap.add(num[i]);
}
result.add(heap.peek());
for(int i = 0,j=i+size;j<num.length;i++,j++){
heap.remove(num[i]);
heap.add(num[j]);
result.add(heap.peek());
}
return result;
}
}
65. 矩阵中的路径
/*
基于回朔法的递归特性,路径可以被开成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,
在与第n个字符对应的格子的周围都没有找到第n+1个字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。
*/
public class Solution {
public boolean hasPath(char[] matrix , int rows ,int cols , char[] str){
//参数校验
if(matrix == null || matrix.length != rows*cols || str == null || str.length <1){
return false;
}
//boolean矩阵来记录是否访问过
boolean[] visited = new boolean[rows*cols];
for (int i = 0; i < visited.length; i++) {
visited[i] = false;
}
//记录结果的数组
int pathLength[] = {0};
//从每个位置都进行搜索一遍
for(int i= 0;i<rows;i++){
for(int j = 0;j<cols;j++){
if(hasPathCore(matrix,rows,cols,str,visited,i,j,pathLength))
return true;
}
}
return false;
}
public static boolean hasPathCore(char[] matrix,int rows ,int cols , char[] str, boolean[] visited,int row,int col,int[] pathLength){
if(pathLength[0] == str.length)
return true;
boolean hasPath = false;
//判断当前位是否合法
if(row>=0 && row<rows && col<cols && col>=0 && matrix[row*cols + col] == str[pathLength[0]]
&&!visited[row*cols + col]) {
visited[row*cols + col] = true;
pathLength[0]++;
//当前位符合判断下一个位置是否符合,不符合当前位则要回溯
//即当前位的上下左右四个位置
hasPath = hasPathCore(matrix, rows, cols, str, visited, row, col - 1, pathLength)
|| hasPathCore(matrix, rows, cols, str, visited, row - 1, col, pathLength)
|| hasPathCore(matrix, rows, cols, str, visited, row, col + 1, pathLength)
|| hasPathCore(matrix, rows, cols, str, visited, row + 1, col, pathLength);
//如果没找到符合的需要回溯
if (!hasPath) {
pathLength[0]--;
visited[row * cols + col] = false;
}
}
return hasPath;
}
}
//简单版直接用index来表示字符串的下标
public class Solution
{
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
int[] flag=new int[matrix.length];
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(helper(matrix,rows,cols,i,j,0,str,flag))
return true;
}
}
return false;
}
public boolean helper(char[]matrix,int rows,int cols,int i,int j,int k,char[]str,int[]flag)
{
//index直接动态计算就不用pathLength[0]来传递当前在字符串的位置
int index=i*cols+j;
if(i<0||i>=rows||j<0||j>=cols||flag[index]==1||matrix[index]!=str[k])return false;
if(k==str.length-1)return true;
flag[index]=1;
if(helper(matrix,rows,cols,i+1,j,k+1,str,flag)
||helper(matrix,rows,cols,i-1,j,k+1,str,flag)
||helper(matrix,rows,cols,i,j+1,k+1,str,flag)
||helper(matrix,rows,cols,i,j-1,k+1,str,flag))
return true;
flag[index]=0;
return false;
}
}
66. 机器人的运动范围
//同样的思路只是这次只从(0,0)点出发且不用回溯,因为只是看能走多远,只需将四个方向上最远的值相加即可
//当然同样需要用boolean来记录那些已经走过的位置
public class Solution {
//同样用回溯法来做
public int movingCount(int threshold, int rows, int cols)
{
//记录是否访问过的boolean矩阵
boolean[][] visited = new boolean[rows][cols];
return countSteps(threshold,rows,cols,0,0,visited);
}
//分别往四个方向扩散,只是这里不需要回溯,因为这里只需一直往前看能走多远然后返回最远的长度即可
private int countSteps(int threshold, int rows, int cols, int row, int col, boolean[][] visited) {
//边界条件和limit条件
if(row>=rows||row<0||col>=cols||col<0||visited[row][col]||bitSum(col)+bitSum(row)>threshold){
return 0;
}
visited[row][col] = true;
return countSteps(threshold, rows, cols, row, col-1, visited)
+ countSteps(threshold, rows, cols, row-1, col, visited)
+ countSteps(threshold, rows, cols, row, col+1, visited)
+ countSteps(threshold, rows, cols, row+1, col, visited)
+1 ;
}
private int bitSum(int number) {
int sum = 0;
while(number!=0){
sum += number%10;
number /= 10;
}
return sum;
}
}
牛客66题题解
最新推荐文章于 2022-04-07 11:35:13 发布