剑指offer总结

剑指offer总结-java版

(1) 二维数值中查找(数组----较难)

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
输入:7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值:true
思路:
1.先判断 target>=每行第一个 && target<=每行最后一个 是否成立。
2.若上述成立,则对每行进行遍历或者二分查找(二分查找需要数组有序才能用),找到则返回true,找不到则判断下一行
3.每一行都没有,最后返回false

采用遍历的方法:
public class Solution {
    public boolean Find(int target, int [][] array) {
        if (array.length == 0 || array[0].length == 0) return false;
        int k = array[0].length;
        for(int i = 0; i < array.length; i++) {
            if(target >= array[i][0] && target <= array[i][k-1]) {
                for(int j = 0; j < k; j++) {
                    if(array[i][j] == target) {
                        return true;
                    }
                 }
            }
        }
        return false;
    }
}        
       
对每行采用二分查找(折半查找)
public class Solution {
    public boolean Find(int target, int [][] array) {
     if (array.length == 0 || array[0].length == 0) return false;
        int k = array[0].length;
        for(int i = 0; i < array.length; i++) {
            if(target >= array[i][0] && target <= array[i][k-1]){
                int left = 0, right = k-1;
                while(left<=right){
                    int mid = (left+right)/2;
                    if(array[i][mid]==target) return true;
                    else if(array[i][mid]>target) right = mid-1;
                    else left = mid+1;
                }
            }
        }
        return false;
    }
}

(2) 替换空格(字符串----较难)

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
输入:“We Are Happy”
返回值:“We%20Are%20Happy”
思路:

  1. 利用String类型的toCharArray()方法,将字符串转为字符数组
  2. 新建一个StringBulider
  3. 对字符数组使用增强for循环和三元运算对空格进行替换,将正确内容添加到StringBuilder对象后面
  4. 根据StringBuilder的toString()方法转为字符串输出
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param s string字符串 
     * @return string字符串
     */
    public String replaceSpace (String s) {
        // write code here
        StringBuilder sb = new StringBuilder();
        for(char c:s.toCharArray()){
            sb.append(c==' '?"%20":c);
        }
        return sb.toString();
    }
}

相关内容:

  1. StringBuilder有四个构造方法:
    new StringBuilder() 空构造方法
    new StringBuilder(String str) 使用字符串构造
    new StringBuilder(charArray seq) 利用字符数组构造
    new StringBuilder(int capacity) 指定最大容量(这个好像没什么用。若是使用时超出容量会自动增加容量)
  2. StringBuilder的几个方法:
    sb.append(“djjd”) 最后面添加字符串
    sb.append(‘d’) 最后面添加字符
    sb.insert(8, “java”) 从下标8位置开始插入字符串
    sb.delete(5, 8) 左闭右开,删除这段范围内容,下标>=8的位置若有内容会自动向前补齐空位
    sb.reverse() 翻转内容
    sb.toString() 转换为字符串

(3) 从尾到头打印链表(链表----较难)

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
输入:
{67,0,24,58}
返回值:
[58,24,0,67]
思路:
需要掌握原地翻转链表,并且会使用ArrayList

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        
        ArrayList res = new ArrayList();
        ListNode pre = null;
        ListNode next = null;
        while(listNode!=null) {
            next = listNode.next;
            listNode.next = pre;
            pre = listNode;
            listNode = next;
        }
        while(pre!=null) {
            res.add(pre.val);
            pre = pre.next;
        }
        return res;
    }
}

相关内容:

  1. ArrayList的基本定义和方法:
    1.1 ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。类似于动态数组。
    ArrayList<> objectName = new ArrayList<>();  // 初始化
    第一个<>中有个字母E的,不给显示。E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型。如数组,字符串,各种对象;以及基本数据类型的包装类Byte, Short, Integer,Long,Float,Double,Character,Boolean等。顺便再贴张图
    在这里插入图片描述
    1.2 ArrayList常用方法:
    list.add() 添加元素
    list.add(index, obj) 在指定索引位置加入元素,代替insert方法
    list.get(index) 根据索引获取元素值
    list.size() 返回list的有效长度
    list.remove(int index) 删除索引位置元素
    list.remove(Object obj) 删除某个对象,如果有多个相同的就只删掉第一个,例如list.remove(Integer.valueOf(13)),先把13变为引用类型,再删除13的包装类对象
    list.set(2, “df”) 替换指定位置对象
    list.contains(obj) 判断元素是否存在ArrayList
    list.isEmpty() 判断是否为空
    list.toArray() 将ArrayList转换为数组
    list.toString() 将ArrayList转换为字符串
    import java.util.Collections; Collections.sort(list)可以对字母或者数字排序

  2. 原地翻转链表
    在这里插入图片描述

(4) 重建二叉树(树,dfs,数组----中等)

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
输入:[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值:{1,2,5,3,4,6,7}
思路:
没啥好说的,就是递归,但有几个小细节,先看代码

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        
        if(pre == null || in == null || pre.length == 0 || in.length == 0 || pre.length != in.length) {
            return null;
        }
        return getRootNode(pre, 0, pre.length - 1, in , 0, in.length - 1);
        
    }
    
    private TreeNode getRootNode(int [] pre, int preStart, int preEnd, int [] in, int inStart, int inEnd){
        if(preStart > preEnd || inStart > inEnd) {
            return null;
        }
        TreeNode root = new TreeNode(pre[preStart]);
        for(int i = inStart; i <= inEnd; i++) {
            if(pre[preStart] == in[i]) {
                root.left = getRootNode(pre, preStart+1, preStart+i-inStart, in, inStart, i-1);
                root.right = getRootNode(pre, preStart+i-inStart+1, preEnd, in, i+1, inEnd);
            }
        }
        return root;
    }

注意细节:

  1. 任何引用型对象默认值都是null,所以数组对象先判断是不是null,再判断是不是非null但数组长度为0。
  2. 递归终止条件,也就是不能再建立新的子树的条件为,任何一个数组的左边界>右边界,此时返回null。
  3. 通过先序的第一个元素值(根节点值)去中序数组中找对应元素值的下标时,for循环中 i 的上下范围最好用inStart,inEnd。这样递归时边界条件好写。而且几个边界条件也有规律,比如代码中的preStart+1, inStart, i-1, preEnd, i+1, inEnd等几乎是现成的不用怎么计算,需要计算的只有两个,但是preStart+i-inStart+1是preStart+i-inStart加上 1 而已,所以需要计算的下标只有一个。
  4. 其他重建二叉树基本也都是这种递归。
  5. 如果两个数组中重复数字咋整?不知道,以后再说

(5) 用两个栈实现队列(栈----简单)

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
输入:无
返回值: 无
思路:
建立两个栈,入栈时则向栈1中进栈(不用验证满没满)。出栈时判断先栈2空不空,不空则出栈;空则先把栈1中所有元素挨个出栈再进栈2后,再从栈2中出栈即可。

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node); //栈没分配大小,不用判断栈是不是满的
    }
    
    public int pop() {
        if(stack2.isEmpty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

相关内容:

  1. 栈初始化时不用指定大小,并且会自动扩容。所以java中只需要判断栈是不是空栈,不判断满不满。
  2. 栈的常用方法
    stack.push(obj)
    stack.pop() 左边可以有对象接受pop()的值,也可以左边空着。即temp=stack.pop()和stack.pop()都可以。
    stack.peek() 查看栈顶元素,但不取出它。
    stack.size() 获取栈的长度,和ArrayList用法一样。但是 [ ] 这种方式定义的数组,求数组长度为array.length。
    stack.isEmpty()==stack.empty(), 为了和ArrayList统一起来好记忆一些,arrayList和Stack判空统一用isEmpty()。
    stack.search(Object obj) 返回值为int, 表示元素距离栈顶距离,以 1 为基数。

(6) 旋转数组的最小数字(二分----简单)

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
输入:[3,4,5,1,2]
返回值:1
思路:
一听说数组有序,或者有序的变种,就知道考查的是二分了。如题所述这样的 有序数组 搬几个元素到末尾或者开头,形成数组的旋转。也可用二分查找,只是实现有些不一样,随机应变。

  1. 正常二分查找:
    while(left<=right)
    mid = (left+right)>>1
    array[mid]==key return mid
    array[mid] > key right=mid-1
    array[mid] < key left=mid+1
  2. 本题中的二分(由于不是查找某值所以没有key;又因为是旋转过的所以具体操作于循环终止条件也有些不一样)。分析过程如下图,具体代码见下面。
    在这里插入图片描述
import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int length = array.length;
        if(length == 0) return 0;
        
        int left = 0, right = length - 1;
        while(left<right){
            int mid = (left+right)>>1;
            if(array[mid]>array[right]){
                left = mid+1;
            } else {
                right = mid;
            }
        }
        return array[right];
    }
}

(7) 斐波那契额数列(数组----入门)

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n≤39
输入:4
返回值:3
思路:没啥好说的

public class Solution {
    public int Fibonacci(int n) {

        if(n == 0 || n == 1)
            return n;
        
//         int [] temp = new int[n+1];
//         temp[0] = 0;
//         temp[1] = 1;
//         for(int i = 2; i<=n; i++){
//             temp[i] = temp[i-1]+temp[i-2];
//         }
//         return temp[n];
        int a = 0;
        int b = 1;
        int c = 0;
        for(int i = 2; i<=n; i++){
            c = a+b;
            a = b;
            b = c;
        }
        return c;
    }
}

(8) 跳台阶(动规,递归----中等)

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
输入:1或者4
返回值:1或者5
思路:
f(n) = f(n-1)+f(n-2)

public class Solution {
    public int jumpFloor(int target) {
        
        if(target <= 0){
            return 0;
        }
        if(target == 1 || target == 2){
            return target;
        }
        int [] temp = new int[target+1];
        temp[0] = 0;
        temp[1] = 1;
        temp[2] = 2;
        for(int i = 3; i<=target; i++){
            temp[i] = temp[i-1]+temp[i-2];
        }
        return temp[target];
    }
}

(9) 变态跳台阶(动规,递归,贪心,数学规律总结----简单)

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
输入:3
输出:4
思路:
因为 f(n) = f(n-1)+f(n-2)+…+f(1)+f(0)
所以 f(n-1) = f(n-2)+f(n-3)+…+f(1)+f(0)
所以 f(n) = f(n-1)+f(n-1)

public class Solution {
    public int jumpFloorII(int target) {
        // 笨方法
//         if(target <= 0){
//             return 0;
//         }
//         if(target == 1){
//             return 1;
//         }
//         int [] temp = new int[target+1];
//         temp[0] = 1;
//         for(int i=1; i<=target; i++){
//             for(int j=0; j<i; j++){
//                 temp[i] += temp[j];
//             }
//         }
//         return temp[target];
//     
//     因为f(n)=f(n-1)+f(n-2)+...+f(1)
//     又因为f(n-1)=f(n-2)+...+f(1)
//     所以 f(n)=f(n-1)+f(n-1)=2*f(n-1),所以简便方法如下
        if(target <= 0) return 0;
        if(target == 1) return 1;
        int sum = 1;
        for(int i=2; i<=target; i++){
            sum*=2;
        }
        return sum;
    }
}

(10) 矩形覆盖(动规,递归----中等)

我们可以用2x1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2x1的小矩形无重叠地覆盖一个2xn的大矩形,总共有多少种方法?比如n=3时,2x3的矩形块有3种覆盖方法:
在这里插入图片描述输入:4
返回值:5
思路:这个问题需要把二维转换成一维的。虽然大矩阵是2xn的形状,但是2xn中的这个2对结果有影响吗。如果竖着填充一个2x1的,那填充一个就可以形成一个完整大矩阵;如果横着填充一个也就是1x2的,那必然也还需要填充另一个横的才能形成一个完整大矩阵,没有别的选择。所以这道题等价于从0开始每次加1或者加2,达到n时有几种情况。又是一个青蛙跳台阶。

public class Solution {
    public int rectCover(int target) {
    
        if(target <= 0) return 0;
        if(target ==1 || target == 2) return target;
        int [] temp = new int[target+1];
        
        temp[1] = 1;
        temp[2] = 2;
        for(int i = 3; i<=target; i++){
            temp[i] = temp[i-1]+temp[i-2];
        }
        return temp[target];
    }
}

(11) 二进制中1的个数(位运算,数学----中等)

输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
输入:10
返回值:2
思路:运用位操作,下面有常用位操作的使用

public class Solution {
    public int NumberOf1(int n) {
        
//         两种投机方式
//         return Integer.bitCount(n);
//         return Integer.toBinaryString(n).replaceAll("0", "").length();
        int sum = 0;
        while(n!=0){
            sum += (n&1);
            n = n>>>1;
        }
        return sum;
    }
}

相关内容:

  1. 计算机中所有数据都是以二进制的形式存储的。位运算就是直接对在内存中的二进制数据进行操作,因此处理数据的速度非常快。常用的有这几种:
    & 与 两个位都为1时才为1
    | 或 有一个1就是1
    ^ 异或 两个位相同为0,相异为1
    ~ 取反 0变1,1变0
    << 左移 各二进位左移若干位,高位丢弃,低位补0
    这样 >> 右移。地位丢弃,对于无符号数高位补0;有符号数大部分补符号位(根据编译器)
    这样 >>> 右移。但高位就是补0
  2. 注意事项:
    只有~是单目运算符,其他都是双目。
    位操作只能用于整形和字符型。byte,short,int,long,char。float和double会报错。
    位操作运算符优先级比较低,最好用括号来保证运算顺序。
    位操作还有些混合运算符:&=,|=,^=,<<=, >>=
    下面位移操作示例:
public class BitMain {

    public static void main(String [] args) {
        int a = -15, b = 15;
        
        // -15 = [1111 0001]补,右移二位,最高位由符号位填充将得到 [1111 1100]补 即 -4
        System.out.println(a >> 2); // -4
        
        // 15= [0000 1111]补,右移二位,最高位由符号位填充将得到 [0000 0011] 即 3
        System.out.println(b >> 2); // 3:
    }
}

常用位操作的小技巧:
首先记住 1 的位是 0000……0001 这个样子的。最后一位是1,其余位均是0。而且可以对 1 进行左移以及取反等操作。左移得到 0000……1……00000,再进一步取反则有1111……0……1111,用1的位以及1的变形位再与其他数字的位进行与或可以产生不同的效果。

  1. 判断奇偶数
    只要根据最末位是1还是0来决定,为0就是偶数,为1就是奇数。可以用 if((a&1)= =0) 代替if(a%2==0)来判断a是不是偶数。
  2. 交换两数,不靠临时变量:
    a^=b;
    b^=a;
    a^=b;
    就可实现两束交换。
    注意:异或满足交换律,一个数和自己的异或为0,任何数与0异或都不变。
  3. 变换符号:
    变换符号只需 取反操作后加1
int a = -15, b = 15;
System.out.println(~a + 1);
System.out.println(~b + 1);
  1. 对于一个整数,可以通过将 1 向左移位后与其相与/或来达到在指定位上置1/置0的效果。
// 从低位到高位.将n的第m位置为1
System.out.println(n | (1<<(m-1)));
/*将1左移m-1位找到第m位,得到000...1...000
n在和这个数做或运算*/

// 从低位到高位,将n的第m位置为0
System.out.println(n & ~(1<<(m-1)));
/* 将1左移m-1位找到第m位,取反后变成111...0...1111
n再和这个数做与运算*/
  1. 取某数的某一位,或者判断某数的某一位是0还是1。将该数右移再和1进行与。
// 从低位到高位,取n的第m位
int m = 2;
System.out.println((n >> (m-1)) & 1);
  1. 求两个整数的平均值
//  求两个整数的平均值
System.out.println((a+b) >> 1);
  1. 判断一个数是不是2的幂
// 判断一个数n是不是2的幂
System.out.println((n & (n - 1)) == 0);
/*如果是2的幂,n一定是100... n-1就是1111....
所以做与运算结果为0*/
  1. 计算2的n次方
// 计算2的n次方 n > 0
System.out.println(2<<(n-1));
  1. 判断符号是否相同
//  判断符号是否相同(true 表示 x和y有相同的符号, false表示x,y有相反的符号。)
System.out.println((a ^ b) > 0);
  1. 乘2,除以2
// 乘以2运算
System.out.println(10<<1);

// 除以2运算(负奇数的运算不可用)
System.out.println(10>>1);
  1. 判断一个数的奇偶性
// 判断一个数的奇偶性
System.out.println((10 & 1) == 1);
System.out.println((9 & 1) == 1);

(12) 数值的整数次方(数学公式总结----中等)

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0。
输入:2,3
返回值:8.00000
思路:多看几遍下图吧
在这里插入图片描述

public class Solution {
    public double Power(double base, int exponent) {
        
        if(exponent == 1) return base;
        if((exponent&1)==0) {
            double temp = power(base, exponent>>1);
            return temp*temp;
        } else{
            double temp = power(base, (exponent-1)>>1);
            return temp*temp*base;
        }
    }
    
    private double power(double base, int exp){
        if(base == 0) {
            if(exp>0) return 0;
            else if(exp == 0) return 0;
            else throw new RuntimeException(); // 会除0错误
        }
        else{
            if(exp > 0) return Power(base, exp); // 你调用我,我再调用你;递归时相互调用
            else if(exp == 0) return 1;
            else return 1/Power(base, -exp);
        }
    }
}

相关内容:
(1)递归快速幂:

// 递归快速幂
int qpow(int a, int n){
    if(n==0){
        return 1; // 0是最小的偶数,返回1
    } else if(n % 2 ==1){
        return qpow(a, n - 1)*a; // 奇数的转换为偶数
    } else {
        int temp = qpow(a, n / 2); // 偶数的转化为更小的偶数,直到为0(最小偶数)
        return temp*temp;
    }
}

(2)递归快速幂+步步求模

// 递归快速幂+步步求模
int qpow(int a, int n){
    if(n==0){
        return 1; // 0是最小的偶数,返回1
    } else if(n % 2 ==1){
        return qpow(a, n - 1)*a % MOD; // 奇数的转换为偶数
    } else {
        int temp = qpow(a, n / 2) % MOD; // 偶数的转化为更小的偶数,直到为0(最小偶数)
        return temp*temp%MOD;
    }
}

(3)非递归快速幂+位操作
在这里插入图片描述

// 非递归快速幂+位操作
int qpow(int a, int n){
    int ans = 0;
    while(n){
        if(n&1){  // 如果n的当前末位位1
            res *= a; // ans乘上当前的a
        }
        a *= a; // a自乘
        n >> 1; // n右移一位
    }
    return ans;
}

上述(1)(2)(3)中传入的参数 a 都是int型的,其实 a 的数据类型支持乘法且满足结合律,快速幂算法都是有效的。

(13) 调整数组顺序使奇数位于偶数前面(数组----较难)

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
输入:[1,2,3,4]
返回值:[1,3,2,4]
思路:
空间换时间。遍历原数组,用两个ArrayList数组分别存放奇数和偶数。最后再整合到一个数组中。

import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param array int整型一维数组 
     * @return int整型一维数组
     */
    public int[] reOrderArray (int[] array) {
        // write code here
//         这两个思路是一样的,只是这个使用了动态初始化的数组;另一个利用了ArrayList。但是
//         这题应该用第二种方法,因为导包已经暗示了,用数组的话都不用导包
//         int len = array.length;
//         int [] x = new int[len];
//         int [] y = new int[len];
//         int x_index = 0;
//         int y_index = 0;
//         for(int i=0;i<len;i++){
//             if((array[i]&1)==1) {
//                 x[x_index] = array[i];
//                 x_index++;
//             } else{
//                 y[y_index] = array[i];
//                 y_index++;
//             }
//         }
//         for(int i=0;i<y_index;i++){
//             x[x_index] = y[i];
//             x_index++;
//         }
//         return x;
        
        
        ArrayList<Integer> x =  new ArrayList<>();
        ArrayList<Integer> y = new ArrayList<Integer>();
        
        for(int i=0;i<array.length;i++){
            if((array[i]&1)==0){
                y.add(array[i]);
            } else{
                x.add(array[i]);
            }
        }
        int [] res = new int[array.length];
        for(int i=0;i<x.size();i++){
            res[i] = x.get(i);
        }
        int index = x.size();
        for(int i=0;i<y.size();i++){
            res[index] = y.get(i);
            index++;
        }
        return res;
    }
}

(14) 链表中倒数第K个节点(链表----中等)

输入一个链表,输出该链表中倒数第k个结点。
输入:{1,2,3,4,5},1
返回值:{5}
思路:
设置两个同时指向链表头的指针;第一个指针向向后走k步,再两个指针同时向后走;当第一个指针到达链尾时,第二个指针就指向倒数第k个。

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        // write code here
        if(pHead==null) return null;
        ListNode pre = pHead;
        ListNode res = pHead;
        int i;
        for(i=0;i<k && pre!=null;i++){
            pre = pre.next;
        }
        
        //这里的判断条件i==k-1若成立则说明上面的for循环是因为pre==null才终止的,表明这个链表不存在倒数第k个
        if(i==k-1) return null;
        while(pre!=null){
            pre = pre.next;
            res = res.next;
        }
        return res;
    }
}

相关内容:和这题无关的一道leetcode题,判断链表中有没有环,采用的快慢指针的方式。两个指针均指向链表头,第一个指针步速为2,第二个指针步速为1,都同时走;若有环,则第一个指针迟早会和第二个指针相遇;并且因为步速差为1,不会说追上后直接越过了而不碰到。

(15) 反转链表(链表----中等)

输入一个链表,反转链表后,输出新链表的表头。
输入:{1,2,3}
返回值:{3,2,1}
思路:
原地翻转链表见第(3)题

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {

        if(head==null || head.next==null) return head;
        ListNode pre = null;
        ListNode next = null;
        ListNode list = head;
        while(list!=null){
            next = list.next;
            list.next = pre;
            pre = list;
            list = next;
        }
        return pre;
    }
}

(16) 合并两个排序的链表(链表----简单)

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
输入:{1,3,5},{2,4,6}
返回值:{1,2,3,4,5,6}
思路:
先挨个比较。等一个链表为空后,还剩下的那个直接添加到新链表中即可。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null) return list2;
        if(list2==null) return list1;
        
        ListNode list;
        ListNode post;
        // 有些人会新建一个节点root当作新链表的表头,最后返回root.next就是题目要求的。这样的好处是
        // 后面的操作可以统一起来,就不需要我的第一个if-else。但是我不想新建一个节点。
        if(list1.val<=list2.val) {
            list = list1;
            post = list1;
            list1 = list1.next;
        } else {
            list = list2;
            post = list2;
            list2 = list2.next;
        }
        while(list1!=null && list2!=null){
            if(list1.val<=list2.val){
                post.next = list1;
                post = list1;
                list1 = list1.next;
            } else {
                post.next = list2;
                post = list2;
                list2 = list2.next;
            }
        }
        if(list1!=null) post.next = list1;
        if(list2!=null) post.next = list2;
        
        return list;
    }
}

(17) 判断某树是否为另一树的子结构(树,递归----较难)

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
输入:{8,8,#,9,#,2,#,5},{8,9,#,2}
返回值:true
思路:
树的问题肯定都是递归,或者再需要一些队列,栈,数组,可变数组等来辅助。(递归时注意设置递归的终止条件)

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean res = false;
        if(root1!=null && root2!=null) {
            if(root1.val==root2.val){
                res = DoesTree1HasTree2(root1, root2);
            }
            if(!res) res = HasSubtree(root1.left, root2);
            if(!res) res = HasSubtree(root1.right, root2);
        }
        return res;
    }
    
    private boolean DoesTree1HasTree2(TreeNode root1, TreeNode root2){
        // 看HasSubTree对DoesTree1HasTree2方法的调用会发现root1,root2已经判断过了,下面的三条各种判断是多余的。
        // 但是DoesTree1HasTree2对自己进行调用时传进来的参数可还没有进行分类判断过。况且递归是需要终止条件的,下面
        // 这三行就是递归必不可少的终止条件。
        if(root1==null && root2!=null) return false;
        if(root2==null) return true;
        if(root1.val==root2.val) {
            return DoesTree1HasTree2(root1.left, root2.left) && DoesTree1HasTree2(root1.right, root2.right);
        } else {
            return false;
        }
    }
    
}

(18) 二叉树的镜像(树,层次遍历,队列,可变数组----简单)

操作给定的二叉树,将其变换为源二叉树的镜像。
比如: 源二叉树
在这里插入图片描述
输入:{8,6,10,5,7,9,11}
返回值:{8,10,6,11,9,7,5}
思路:
用队列对二叉树进行层次遍历,遍历的同时对每个节点的两棵子树进行交换。

import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pRoot TreeNode类 
     * @return TreeNode类
     */
    public TreeNode Mirror (TreeNode pRoot) {
        // write code here
        if(pRoot==null) return pRoot;
        
//         ArrayList<TreeNode> list = new ArrayList<>();
//         Stack<TreeNode> stack = new Stack<TreeNode>();
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode root;
        TreeNode temp;
        queue.offer(pRoot);
        while(!queue.isEmpty()){
            root = queue.poll();
            temp = root.right;
            root.right = root.left;
            root.left = temp;
            if(root.left!=null) queue.offer(root.left);
            if(root.right!=null) queue.offer(root.right);
        }
        return pRoot;
        
    }
}

相关内容:

  1. Queue只是一个接口,不像Stack是一个具体类可以直接new。但LinkedList实现了Queue,我们就是把LinkedList当作队列使用,使用时用多态。Queue<类型> queue = new LinkedList<>();
  2. 队列常用方法:
    queue.offer(obj) 在队尾添加元素,比queue.add(obj)更安全。队满时前者返回false不会抛异常
    queue.poll() 返回并删除队首元素,比queue.remove()更安全。队空时前者返回null不会抛异常
    queue.peek() 返回队首元素但不删除,比queue.element()安全。队空时前者返回null不会抛异常

(19) 顺时针打印矩阵(数组,四个方向的控制变量----较难)

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
输入:[[1,2],[3,4]]
返回值:[1,2,4,3]
思路:
一开始想根据m*n的二维数组的长宽来确定共有多少层;然后设置一个变量 k 来记录处于第几层,然后从外圈向内圈一层层进去。发现漏洞百出还麻烦,基本实现不了题目要求。后来看到了,设置四个变量top,bottom,left,right分别控制四个方向;并且通过控制这四个变量条件来保证从上到下和从下到上不会重复打印,从左到右与从右到左也不会重复打印。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       
        ArrayList<Integer> res = new ArrayList<>();
        int m = matrix.length;
        int n = matrix[0].length;
        
        if(m==0 || n==0) return res;
        
        int top=0,left=0,right=n-1,bottom=m-1;
        while(top<=bottom && left<=right){
            // 左到右
            for(int i=left;i<=right;i++) res.add(matrix[top][i]);
            // 上到下
            for(int i=top+1;i<=bottom;i++) res.add(matrix[i][right]);
            // 右到左
            for(int i=right-1;i>=left && bottom>top;i--) res.add(matrix[bottom][i]);
            // 下到上
            for(int i=bottom-1;i>top && right>left; i--) res.add(matrix[i][left]);
            top++;left++;right--;bottom--;
        }
        return res;
        
    }
}

(20) 包含min函数的栈(辅助最小元素栈----较难)

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
输入:无
返回值:无
思路:
看到这个问题,我们可能会想,添加一个成员保存最小元素,每次压栈时如果压栈元素比当前最小元素更小,就更新最小元素。
但是有一个问题,如果最小元素被弹出来了呢?怎么得到次小元素?分析至此可以发现,仅仅添加一个成员变量存放最小元素是不够的,我们需要在最小元素弹出后可以得到次小元素,次小弹出后可以得到次次小元素。
因此,用另一个辅助栈再合适不过,叫它最小元素栈。

方法:每次压栈时,也判断下最小元素栈,若最小元素栈为空则也向最小元素栈里压入,或者不为空但是压入元素小于等于最小元素栈栈顶元素则也压入。弹栈时,如果弹出的元素和最小元素栈的栈顶元素相等,就也把最小元素栈的栈顶也弹出。

import java.util.Stack;

public class Solution {

    private Stack<Integer> s = new Stack<>();
    private Stack<Integer> sMin = new Stack<>();
    
    public void push(int node) {
        s.push(node);
        if(sMin.empty()){
            sMin.push(node);
        } else{
            if(node<=sMin.peek()) {
                sMin.push(node);
            }
        }
    }
    
    public void pop() {
        if(s.peek()==sMin.peek()){
            sMin.pop();
        }
        s.pop();
    }
    
    public int top() {
        return s.peek();
    }
    
    public int min() {
        return sMin.peek();
    }
}

和Integer相关的内容:

  1. Integer.MAX_VALUE和Integer.MIN_VALUE是Integer的两个静态属性。Byte,Short,Long,Float,Double都有这两个属性,比如 Float.MIN_VALUE。
  2. 构造方法:Integer i = new Integer(100);或者用自动装箱。
  3. Integer.parseInt(str) 把String转成int型
  4. Integer.valueOf(str) 把String转成Integer型
  5. Integer.toString() 或者Integet+"" 就可以把Integer转为字符串类型

(21) 栈的压入-弹出序列(栈----中等)

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
输入:[1,2,3,4,5],[4,3,5,1,2]
返回值:false
思路:借用一个辅助栈,遍历压栈顺序。先将第一个元素压入辅助栈,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1!=4;所以我们继续压栈,直到相等后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,知道不相等,这样循环等压栈顺序遍历完成,如果辅助栈不为空,说明弹出序列不是该栈的弹出顺序。

import java.util.ArrayList;
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        Stack<Integer> stack = new Stack<>(); // 辅助栈
        int j=0;
        for(int i=0;i<pushA.length;i++){
            stack.push(pushA[i]);
            while(!stack.isEmpty() && stack.peek()==popA[j]){
                stack.pop();
                j++;
            }
        }
        if(!stack.isEmpty()) return false;
        return true;
    }
}

(22) 从上往下打印二叉树(树,队列----简单)

从上往下打印出二叉树的每个节点,同层节点从左至右打印。
输入:{5,4,#,3,#,2,#,1}
返回值:[5,4,3,2,1]
思路:新建一个队列,或者用可变数组模拟队列(可变数组ArrayList能模拟队列和栈)。然后对树进行层次遍历。

import java.util.ArrayList;
// import java.util.Queue;
// import java.util.*;

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
//         ArrayList<Integer> res = new ArrayList<>();
//         if(root==null) return res;
//         建立一个队列
//         Queue<TreeNode> queue = new LinkedList<>();
//         queue.offer(root);
//         while(!queue.isEmpty()){
//             TreeNode temp = queue.poll();
//             res.add(temp.val);
//             if(temp.left!=null) queue.offer(temp.left);
//             if(temp.right!=null) queue.offer(temp.right);
//         }
//         return res;
        
        // 用ArrayList模拟队列
        ArrayList<Integer> res = new ArrayList<>();
        if(root==null) return res;
        ArrayList<TreeNode> queue = new ArrayList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode temp = queue.get(0);
            res.add(temp.val);
            queue.remove(0);
            if(temp.left!=null) queue.add(temp.left);
            if(temp.right!=null) queue.add(temp.right);
        }
        return res;
    }
}

(23) 是否为二叉树的后续遍历序列(树,递归----较难)

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜素树)
输入:[4,8,6,12,16,14,10]
返回值:true
思路:只要涉及树的,如果不用空间换时间等其他辅助数据结构如队列、栈、数组的帮忙。基本都是递归,以及少数的层次遍历。这道题肯定也是递归。
二叉搜索树的后序遍历序列中,最后一个数字是树的根节点,数组中前面数字可以分为两部分:第一部分是左子树节点的值,都比根节点小;第二部分是右子树节点的值,都比根节点值大。先判断数组最后一个节点是不是根结点,再根据根节点值把前面数组分为两个部分,对着两个部分分别递归下去。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        // 采用分治法的思想,找到根节点、左子树和右子树的序列。分别判断左右子序列是否为二叉树的后序序列。
        
        // 先从小到大排序得到中序的序列;再根据中序和后续序列进行二叉树的恢复和生成,看能不能恢复出一棵树。
        // 这个思路首先就太曲折了。其次能不能实现题目要求都很难说。
        
        if(sequence==null || sequence.length==0) return false;
        if(sequence.length==1) return true;
        return judge(sequence, 0, sequence.length-1);
    }
    
    private boolean judge(int [] seq, int left, int right){
//         if(left>=right) return true; // 这个递归终止条件
//         int j=right;
//         while(j>left && seq[j-1]>seq[right]){
//             j--;
//         }
//         for(int i=left;i<j;i++){
//             if(seq[i]>seq[right])
//                 return false;
//         }
//         return judge(seq,left,j-1) && judge(seq,j,right-1);
        // 注释和下面的这两种写法都可以,就是一些控制条件细节上不同
        if(left>=right) return true;
        int j = right-1;
        for(;j>=left&&seq[j]>seq[right];j--);
        for(int i=left;i<j;i++){
            if(seq[i]>seq[right])
                return false;
        }
        return judge(seq,left,j) && judge(seq,j+1,right-1);
    }
}

相关内容:
本题(23)和后面的(27)题都是利用递归解决问题,在(27)有点关于递归的小反思。

(24) 二叉树中和为某一值得路径(递归----较难)

输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
输入:{10,5,12,4,7},22 和 {10,5,12,4,7},15
返回值:[[10,5,7],[10,12]] 和 []
思路:
采用先序遍历并且递归,每访问一个节点就更新一次从根节点到本节点的路径和,如果本节点是叶子节点则判断路径和是否等于目标值。

import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {

    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        ArrayList<Integer> temp = new ArrayList<>();
        int sum=0;
        if(root==null) return res;
        Find(root, target, sum, res, temp);
        return res;
    }
    
    public void Find(TreeNode root, int target, int sum, ArrayList<ArrayList<Integer>> res, ArrayList<Integer> temp){
        temp.add(root.val);
        sum+=root.val;
        if(root.left==null&&root.right==null){
            if(sum==target)
            // 此处必须新建一个ArrayList,否则添加到res中的temp还是内存中一直操作的那一个,
            // 已经添加到res中的temp后会被后面的操作破坏掉
                res.add(new ArrayList(temp));
        }
        if(root.left!=null) Find(root.left, target, sum, res, temp);
        if(root.right!=null) Find(root.right, target, sum, res, temp);
        temp.remove(temp.size()-1);
//         sum-=root.val;
        //上面这行代码可有可无,因为对于基本数据类型,参数传递的是值的一个拷贝。在本次调用中改变了值也不会影响调用者的数值
    }
}

注意事项:

  1. 对于基本数据类型,参数传递的是值的拷贝,在被调用者中修改其值完全不会影响调用者中的该值。
  2. 对于引用类型,参数传递的是引用,也就是说不管递归了多少次,传来传去了多少次,内存中只有一个真实对象,每次传参相当于新复制了一个指向真实对象的引用(或者叫指针吧,java没有指针的概念)。
  3. temp是一个ArrayList对象,new ArrayList(temp) 可以当克隆来用。

(25) 复杂链表的复制(链表----较难)

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
输入:无
返回值:无
思路:
方法1:

  1. 新建一个HashMap,键和值都是RandomListNode类型。
  2. 根据原链表表头的label新建一个节点,当作新链表的表头
  3. 将原表头作为健,新表头作为值添加到hashMap中
  4. 分别为两个链表设置一个可以移动的指针
  5. 用上面的指针按照next的顺序遍历原链表,假设现在遍历过程中正在访问节点A:若A.next不为空并且哈希表中不存在键为A.next的键值对,则根据A.next的label新建new_A.next,然后将这一对添加进哈希表;最后将new_A.next连接到新链表的末尾。若A.random不空并且哈希表中不存在键为A.random的键值对,则·……具体操作同A.next。

方法2:

  1. 遍历一遍原链表,根据原链表的label和next完全建立一条新的链表,建立过程中不考虑random的影响。
  2. 再遍历一次原链表,若某节点的random不为空,则计算该节点的random指向的节点距离原链表的表头有多远,知道这个距离了,也就知道新链表中对应节点的random距离新链表表头的距离。
/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.HashMap;

public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead==null) return null;
        // 保持原链表节点和复制链表节点的对应
        HashMap<RandomListNode, RandomListNode> hashMap = new HashMap<>();
        RandomListNode root =  new RandomListNode(pHead.label);
        hashMap.put(pHead, root);
        // 移动的指针
        RandomListNode pTemp = pHead;
        RandomListNode rTemp = root;
        while(pTemp!=null){
            // 为next节点赋值
            if(pTemp.next!=null){
                if(!hashMap.containsKey(pTemp.next)){
                    hashMap.put(pTemp.next, new RandomListNode(pTemp.next.label));
                }
                rTemp.next=hashMap.get(pTemp.next);
            }
            // 为random节点赋值
            if(pTemp.random!=null){
                if(!hashMap.containsKey(pTemp.random)){
                    hashMap.put(pTemp.random, new RandomListNode(pTemp.random.label));
                }
                rTemp.random = hashMap.get(pTemp.random);
            }
            // 移动指针
            pTemp = pTemp.next;
            rTemp = rTemp.next;
        }
        
        return root;
        
        // 双层遍历的朴素方法
//         if(pHead==null) return null;
//         RandomListNode root = new RandomListNode(pHead.label);
        
//         RandomListNode pTemp = pHead;
//         RandomListNode rTemp = root;
//         while(pTemp!=null){
//             if(pTemp.next!=null){
//                 rTemp.next = new RandomListNode(pTemp.next.label);
//             }
//             rTemp = rTemp.next;
//             pTemp = pTemp.next;
//         }
        
//         pTemp = pHead;
//         rTemp = root;
//         while(pTemp!=null&&rTemp!=null){
//             if(pTemp.random!=null){
//                 RandomListNode pTemp1 = pHead;
//                 RandomListNode rTemp1 = root;
//                 while(pTemp1!=pTemp.random){
//                     pTemp1 = pTemp1.next;
//                     rTemp1 = rTemp1.next;
//                 }
//                 rTemp.random = rTemp1;
//             }
//             pTemp = pTemp.next;
//             rTemp = rTemp.next;
//         }
        
//         return root;
    }
}

(26) 二叉搜索树创建双向链表(空间换时间----中等)

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
输入:无
返回值:无
思路:
空间换时间。中序遍历该搜索树,将每个节点按顺序添加到可变数组中(注意添加进数组的是引用型对象,所以添加进数组的只是每个节点的引用的拷贝)。之后遍历该数组,进行left,right指向的操作。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.*;

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null) return null;
        ArrayList<TreeNode> list = new ArrayList<>();
        inOrder(pRootOfTree, list);
        for(int i=0;i<list.size()-1;i++){
            list.get(i).right = list.get(i+1);
            list.get(i+1).left = list.get(i);
            if(i==0) list.get(i).left = null;
            if(i==list.size()-2) list.get(list.size()-1).right=null;
        }
        return list.get(0);
    }
    
    public void inOrder(TreeNode root, ArrayList<TreeNode> list){
        if(root==null) return;
        inOrder(root.left, list);
        list.add(root);
        inOrder(root.right, list);
    }
}

(27) 字符串的所有排列可能以字典序输出(递归,字符串----较难)

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入:“ab”
返回值:[“ab”,“ba”]
思路:
递归法,问题转换为先固定住第一个字符,求剩余字符的排列;求剩余字符排列时跟原问题一样。

  1. 遍历出所有可能出现在第一个位置的字符(即:一次将第一个字符同后面所有字符交换);
  2. 固定第一个字符,求后面字符的排序(即:在第1步的遍历过程中,插入递归进行排序)。

注意几点:

  1. 先确定递归结束的条件,例如本例中可设left==str.size()-1;
  2. 形如aba或aa等特殊情况,需要自己判断是否有重复的
  3. 输出的排列可能不是按照字典序排列的,最终输出前可以排下序
  4. 同时注意下,递归时传的参数是引用型还是基本型
import java.util.ArrayList;
import java.util.Collections;

public class Solution {
    public ArrayList<String> Permutation(String str) {
       if(str==null || str.length()==0) return null;
        ArrayList<String> res = new ArrayList<>();
        char [] c = str.toCharArray();
        Solve(c, res, 0);
        Collections.sort(res);
        return res;
    }
    
    public void Solve(char [] c, ArrayList<String> res, int left){
        if(left==c.length-1){
            String ans = new String(c); // 这里用  c.toString()会报错,必须得new
            if(!res.contains(ans)){
                res.add(ans);
            }
        } else {
            for(int i=left;i<c.length;i++){
                Swap(c, left, i);
                Solve(c, res, left+1);
                Swap(c, left, i); // 交换的字母复位
            }
        }
    }
    
    public void Swap(char [] c, int i, int j){
        char temp = c[i];
        c[i] = c[j];
        c[j] = temp;
    }
    
}

相关内容:
1.虽然常用递归写题,但是自我感觉没有掌握递归的核心,真正掌握之后的大佬,不管多复杂的情况,总能把问题摸清楚然后由大化小。有两点比较重要:(1)分解问题:是怎么由大问题变为同类型的小问题,缩小问题规模;(2)终止递归条件:是靠什么来缩小问题的规模,而靠的这个东西同样也是我们判断递归终止条件的东西。

2.对于树,每个节点都带有–>箭头,递归时要缩小规模只需要根据箭头传入下一个节点就行,而传入的新节点不为null就是递归终止条件了。
3.若是array,str这种,怎么缩小规模呢?靠两个/一个,或者多个下标索引等数值来表明起始/终点位置,达到缩小规模的目的;而缩小规模需要的索引等对象也正是我们终止递归的判断条件。
在这里插入图片描述

(28) 数组中出现次数超过一半的数字(数组----中等)

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
输入:[1,2,3,2,2,2,5,4,2]
返回值:2
思路:

  1. 一种方法是对数组排序,不论数组长度是奇是偶,若有过半的数字,则一定是中间那个数字。但是这种方法求出中间数字后需要验证,因为假如根本不存在过半的数字呢?

  2. 另一种时间复杂度更低的方法,见图中例子结合代码
    在这里插入图片描述

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        
        int res=array[0], cnt=1;
        for(int i=1;i<array.length;i++){
            if(array[i]==res) cnt++;
            else{
                cnt--;
                if(cnt==0){
                    res = array[i];
                    cnt=1;
                }
            }
        }
        int count=0;
        for(int i=0;i<array.length;i++){
            if(array[i]==res) count++;
        }
        return count>array.length>>1?res:0;
    }
}

(29) 最下的K个数(优先级队列,堆----中等)

给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。如果K>数组的长度,那么返回一个空的数组
输入:[4,5,1,6,2,7,3,8],4
返回值:[1,2,3,4]
思路:
数组,字符串中求前K小,前K大的,一般都是用堆的方法,可以用java中现成的堆。

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        
        ArrayList<Integer> res = new ArrayList<>();
        if(k>input.length) return res;
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        
        // 下面是怎么实现大顶堆(k代表初始化容量),new一个比较规则(匿名对象)当参数
//         PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>(){
            
//             @Override
//             public int compare(Integer o1, Integer o2){
//                 return o2-o1; // 或者 return o2.compareTo(o1);
//             }
//         });
        
        for(int i=0;i<input.length;i++){
            minHeap.offer(input[i]);
        }
        for(int i=0;i<k;i++){
            res.add(minHeap.poll());
        }
        return res;
    }
}

相关内容:
java在1.5版本后,提供了具备小根堆性质的数据结构,也就是优先级队列PriorityQueue。同时根据PriorityQueue,变化一下就能实现大根堆。PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组,逻辑结构层次遍历的结果刚好是一个数组。如下图

在这里插入图片描述
堆的常用方法:
heap.offer(obj) 添加对象,比heap.add(obj)更推荐
heap.poll() 返回并删除堆的顶部元素,比heap.remove()更推荐
注意事项:
堆,即优先级队列中不能存放空元素;
加入元素后如果堆,也就是数组的大小不够会自己进行扩充.

(30) 连续子数组的最大和(动规,数组----中等)

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).
输入:[1,-2,3,10,-4,7,2,-5]
返回值:18
例如:输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18。
思路:
方法1:
假设dp[i] 表示以元素array[i]结尾的最大连续子数组和.
以[-2, -3, 4, -1, -2, 1, 5, -3]为例可以发现:
dp[0] = -2
dp[1] = -3
dp[2] = 4
dp[3] = 3
可以发现 dp[ i ] = max{ dp[ i - 1 ] + array[ i ], array[ i ] }

方法2:
二维动态数组.具体不写啦

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        
        // 二维动态数组方法,时间复杂度O(n*n)
//         int [][] temp = new int[array.length][array.length];
//         int max=Integer.MIN_VALUE;
//         for(int i=0;i<array.length;i++){
//             temp[i][i] = array[i];
//         }

//         for(int i=0;i<temp.length;i++){
//             for(int j=i+1;j<temp[0].length;j++){
//                 temp[i][j]=temp[i][j-1]+array[j];
//             }
//         }
//         for(int i=0;i<temp.length;i++){
//             for(int j=i;j<temp[0].length;j++){
//                 max=temp[i][j]>max?temp[i][j]:max;
//             }
//         }
//         return max;
        
        // 一维动态数组,时间复杂度O(n)
        int [] dp  = new int[array.length];
        dp[0] = array[0];
        for(int i=1;i<array.length;i++){
            dp[i] = (dp[i-1]+array[i])>array[i]?dp[i-1]+array[i]:array[i];
        }
        int max = Integer.MIN_VALUE;
        for(int i=0;i<dp.length;i++){
            max = max>dp[i]?max:dp[i];
        }
        return max;
    }
}

(31) 整数中1出现的次数(从1到n整数中1出现的次数)(数学规律----中等)

求出1-13的整数中1出现的次数,并算出100-1300的整数中1出现的次数?为此他特别数了一下1-13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
输入:13
返回值:6
思路:见图
在这里插入图片描述

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
//         蠢方法(还有一种笨方法是对每个整数进行多次对10的取模和整除,对余数进行判断)
//         char target  ='1';
//         int cnt = 0;
//         for(int i=1;i<=n;i++){
//             String str = i+"";
//             for(char c:str.toCharArray()){
//                 if(c==target)
//                     cnt++;
//             }
//         }
//         return cnt;
        
        int high=n, temp, cur, low;
        int i=1;
        int cnt = 0;
        while(high!=0){
            high = n / (int)Math.pow(10, i);
            temp = n % (int)Math.pow(10, i);
            cur = temp / (int)Math.pow(10, i-1);
            low = temp % (int)Math.pow(10, i-1);
            if(cur==1){
                cnt+=high*Math.pow(10, i-1) + low + 1;
            } else if(cur<1){
                cnt+=high*Math.pow(10, i-1);
                // java.lang.Math包不用导入,默认导入的.Math.pow(),Math.abs()等可以直接用
            } else{
                cnt+=(1+high)*(int)Math.pow(10, i-1);
            }
            i++;
        }
        return cnt;
    }
}

(32) 把数组排成最小的数(数组----较难)

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
输入:[3,32,321]
返回值:“321323”
思路:
先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来.关键是制定排序规则.排序规则如下:
若 ab > ba 则 a > b
若 ab < ba 则 a < b
若 ab = ba 则 a = b
如 2 < 21,但是"221" > “212”,所以要将两者拼接起来进行比较.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        
        ArrayList<String> temp = new ArrayList<>();

        for(int i=0;i<numbers.length;i++){
            temp.add(numbers[i]+"");
            // int型先转为字符串,可以方便拼接和比较操作.要不两个int型进行拼接
            // 得乘上好多次10的次幂,除了方便操作外,最终返回值也要求是字符串
        }
        // Collections.sort(obj) 默认对对象进行字典序排序
        // Collections.sort(obj, new一个比较器的重新实现的匿名对象作为参数),
        // 可以实现自定义比较规则.
        // 而对于ArrayList,list.sort(new一个比较器的重新实现的匿名对象作为参数)才能用
        // list.sort()不能用,必须传入比较规则
        Collections.sort(temp, new Comparator<String>(){
            @Override
            public int compare(String s1, String s2){
                String temp1 = s1+s2;
                String temp2 = s2+s1;
                return temp1.compareTo(temp2);
            }
        });
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<temp.size();i++){
            sb.append(temp.get(i));
        }
        return sb.toString();
    }
}

注意事项:

  1. 记住怎么自定义比较规则,还有o1.compareTo(o2)是小根堆排序,由小到大. o2.compareTo(o1)是大根堆,由大到小.
  2. StringBuilder的使用(其实字符串直接用加号拼接更推荐)

(33) 丑数(数学----中等)

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
输入:7
返回值:8
思路:
假如 1 是第一个丑数,因为丑数的质因数只能有2, 3, 5,所以1x2, 1x3,1x5也都是后面的丑数;而2x2,2x3,2x5也都是后面的丑数.简而言之就是每个丑数乘以2,3,5就是后面的丑数,计算出来的新丑数再都乘以2,3,5又是更新的丑数.现在问题是怎么确定谁是第N个?先产生一大批丑数,然后排序的方法行不通的.具体怎么做看代码多记两边.

import java.util.ArrayList;
import java.util.Collections;

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=0) return 0;
        
        ArrayList<Integer> res = new ArrayList<>();
        res.add(1);
        int p2 = 0, p3=0,p5=0;
        while(res.size()<index){
            int min = getMin(res.get(p2)*2, res.get(p3)*3, res.get(p5)*5);
            res.add(min);
            // 这三个if有可能进入多个,说明有可能正好有多个都是最小的
            if(res.get(p2)*2==min) p2++;
            if(res.get(p3)*3==min) p3++;
            if(res.get(p5)*5==min) p5++;
        }
        return res.get(index-1);

    }
    
    public int getMin(int a, int b, int c){
        int temp = 0;
        temp = a < b?a:b;
        temp = temp<c?temp:c;
        return temp;
    }
}

(34) 第一个只出现一次的字符位置(哈希表,字符的加减操作----简单)

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
输入:“google”
返回值:4
思路:
方法1:
用HashMap保存每个字符出现的次数,然后再顺序遍历字符串转换成的字符数组,用每个字符作为健去哈希表中查询,看哪个字符对应的值是 1 就行.
方法2:
因为字符类型是基本数据类型,可以直接用于加减运算.比如 ‘a’ - ‘A’, ‘a’ + 23, ‘b’ - 10等;又因为所有字母中’A’是ASCII码最小的,所以所有 字符 - ‘A’ 求出的差值可以当作各个字符在数组中的下标使用.

import java.util.HashMap;

public class Solution {
    public int FirstNotRepeatingChar(String str) {
//         用HashMap保存每个字符出现的次数
//         if(str==null || str.length()==0){
//             return -1;
//         }
//         char [] c = str.toCharArray();
//         HashMap<Character, Integer> hash = new HashMap<>();
//         for(int i=0;i<c.length;i++){
//             if(!hash.containsKey(c[i])){
//                 hash.put(c[i],1);
//             } else {
//                 hash.put(c[i], hash.get(c[i])+1); // 直接重新给key=c[i]赋新值就相当于更新哈希表啦
//             }
//         }
//         for(int i=0;i<c.length;i++){
//             if(hash.get(c[i])==1)
//                 return i;
//         }
//         return -1;
        
        
        // 字符是基本基本数据类型,可以直接和其他字符或者整型相加减
        if(str==null || str.length()==0){
            return -1;
        }
        int [] res =  new int[60];
        char [] temp = str.toCharArray();
        for(char c:temp){
            res[c-'A']++;
        }
        for(int i=0;i<temp.length;i++){
            if(res[temp[i]-'A']==1)
                return i;
        }
        return -1;
    }
}

(35) 数组中的逆序对(分治,递归----困难)

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007.

对于50%的数据,size <= 10^4
对于75%的数据,size <= 10^5
对于100%的数据,size <= 2*10^5

输入描述:题目保证输入的数组中没有的相同的数字
示例:[1,2,3,4,5,6,7,0]
返回值:7
思路:
先把数组分割成子数组,先统计出子数组内部的逆序对数目,然后再统计出两个相邻子数组之间的逆序对的数目.在统计逆序对的过程中,还需要对数组进行排序.就是修改版的归并排序;需要注意的是要对中间的每个返回的数值先求余数(不要最后再求余数).举个例子具体如下:
在这里插入图片描述

  1. 首先根据数组左右边界low, high求出mid, 根据mid将数组分成两个部分[7, 5], [6, 4];
  2. 然后将左右两部分同样方法递归下去,直到每份只有一个元素为止,此时递归终止条件为low>=high;可以得到[7], [5]; [6], [4];
  3. [7], [5]先进行比较,得到一个逆序对;然后合并这俩,并且要升序合并. 同理[6], [4]得到一个逆序对,并升序合并.
  4. 再比较[5, 7], [4, 6]有几个逆序对, 因为 7 > 6 ,所以 7 大于 6 以及 6 之前所有的(即 4 ),可以得到两个逆序对;5 大于 4 , 所以 5 大于 4 及 4 之前所有的(这里没有),可以得到一个逆序对.
  5. 最后合并成[4, 5, 6, 7]完整的升序数组.
public class Solution {
    public int InversePairs(int [] array) {
        if(array==null || array.length==0)
            return 0;
        int [] copy = new int[array.length];
        return Solve(array, copy, 0, array.length-1);
    }
    
   
    public int Solve(int [] arr, int [] copy, int low, int high){
        if(low>=high) //缩小规模,递归终止条件
            return 0;
        int mid = (low+high)>>1;
        int left = Solve(arr, copy, low, mid)%1000000007;
        int right = Solve(arr, copy, mid+1, high)%1000000007;
        
        int i=mid, j=high,copylocl=high,cnt=0;
        while(i>=low && j>mid){
            if(arr[i]>arr[j]){
                cnt+=(j-mid);
                copy[copylocl--] = arr[i--];
                if(cnt>=1000000007){
                    cnt%=1000000007;
                }
            } else{
                copy[copylocl--] = arr[j--];
            }
        }
        for(;i>=low;i--){
            copy[copylocl--] = arr[i];
        }
        for(;j>mid;j--){
            copy[copylocl--] = arr[j];
        }
        for(int k=low;k<=high;k++){
            arr[k] = copy[k];
        }
        return (left+right+cnt)%1000000007;
    }
}

(36) 两个链表的第一个公共节点(链表----中等)

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
输入:无
返回值:无
思路:
方法1:先确定两个链表各自长度,求出长度差,让长的链表先走几步后两个链表一起走,就会同时达到公共节点。
方法2:看下图吧在这里插入图片描述

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//          老实方法
//         ListNode p1 = pHead1;
//         ListNode p2 = pHead2;
//         int len1=0, len2=0;
//         while(p1!=null || p2!=null){
//             if(p1!=null){
//                 len1++;
//                 p1=p1.next;
//             }
//             if(p2!=null){
//                 len2++;
//                 p2=p2.next;
//             }
//         }
//         p1 = pHead1;
//         p2 = pHead2;
//         if(len1>=len2){
//             for(int i=0;i<len1-len2;i++){
//                 p1=p1.next;
//             }
//             for(int i=0;i<len2;i++){
//                 if(p1==p2) break;
//                 p1=p1.next;
//                 p2=p2.next;
//             }
//         } else{
//             for(int i=0;i<len2-len1;i++){
//                 p2=p2.next;
//             }
//             for(int i=0;i<len1;i++){
//                 if(p1==p2) break;
//                 p1=p1.next;
//                 p2=p2.next;
//             }
//         }
//         return p1;
        
        if(pHead1==null || pHead2==null) return null;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1=p1.next;
            p2=p2.next;
            if(p1==p2) return p1;
            if(p1==null) p1=pHead2;
            if(p2==null) p2=pHead1;
        }
        return p1;
    }
}

(37) 数字在排序数组中出现的次数(二分----中等)

统计一个数字在升序数组中出现的次数。
输入:[1,2,3,3,3,3,4,5],3
返回值:4
思路:
看到有序数组基本都是直接用二分,或者二分的改良。二分改良也不行,可以考虑借鉴下二分的思想,比如low, high, mid,双下标索引,双指针的应用。说了是排序数组就要先考虑二分和改良能不能用才行。
本题中对二分进行了两个不同的修改,分别用来找出重复数字第一次和最后一次出现的位置,然后两个位置相减得到最后结果。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       // 按顺序遍历方法
//         int cnt=0;
//         for(int i=0;i<array.length;i++){
//             if(array[i]==k) cnt++;
//             if(array[i]>k) break;
//         }
//         return cnt;
        
        
        if(array==null || array.length==0)
            return 0;
        // 递归和循环都可以实现二分
        int firstK = getFirstK(array, k, 0, array.length-1);
        int lastK = getLastK(array, k, 0, array.length-1);
        if(firstK!=-1 && lastK!=-1){
            return lastK-firstK+1;
        }
        return 0;
    }
    
    private int getFirstK(int [] arr, int k, int low, int high){
        if(low>high)
            return -1;
        int mid = (low+high)>>1;
        if(arr[mid]>k){
            return getFirstK(arr, k, low, mid-1);
        } else if(arr[mid]<k){
            return getFirstK(arr, k, mid+1, high);
        } else if(mid-1>=0 && arr[mid-1]==k) {
        // 意思是:mid位置为k,但是mid位置左边还有数并且也等于k,则high=mid-1
        // 这是对二分的第一个改良
            return getFirstK(arr, k, low, mid-1);
        } else {
            return mid;
        }
    }
    
    private int getLastK(int [] arr, int k, int low, int high){
        int mid = (low+high)>>1;
        while(low<=high){
            if(arr[mid]>k){
                high = mid-1;
            } else if(arr[mid]<k){
                low = mid+1;
            } else if(mid+1<arr.length && arr[mid+1]==k){
            // 意思是:mid位置为k,但是mid位置右边还有数并且也等于k,则low=mid+1
            // 对二分的第二个改良
                low = mid+1;
            } else {
                return mid;
            }
            mid = (low+high)>>1;
        }
        return -1;
    }
}

(38) 二叉树的深度(树,递归----中等)

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
输入:{1,2,3,4,5,#,6,#,#,7}
返回值:4
思路:求树的深度,用前序遍历进行递归(后序遍历也可以,我用习惯前序求树深了)

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.ArrayList;

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root==null)
            return 0;
        ArrayList<Integer> res = new ArrayList<>();
        res.add(0);
        int temp=0;
        getDepth(root, res, temp);
        return res.get(0);
    }
    
    
    private void getDepth(TreeNode root, ArrayList<Integer> res, int temp){
        if(root==null)
            return;
        temp+=1;
        if(root.left==null && root.right==null){
            int tmp = res.get(0)>temp?res.get(0):temp;
            res.remove(0);
            res.add(tmp);
        }
        getDepth(root.left, res, temp);
        getDepth(root.right, res, temp);
    }
    
}

(39) 平衡二叉树(后序遍历----中等)

输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
输入:{1,2,3,4,5,6,7}
返回值:true
思路:
用后序遍历求这棵树的深度,如果在递归过程中发现某节点的左子树深度left和右子树的深度right差值超过了 1 ,则向上一层返回 -1 而不返回树深;如果该节点左右子树平衡,那么就把求得的树深返回上一级。等递归最终完成,如果返回的是 -1 说明不平衡,返回的其他值就是这棵树的深度。这个方法又能求树深又能判断平不平衡。

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        
        // 返回正常深度,说明是平衡的,如果返回的是-1,则不是平衡的
        if(root==null)
            return true;
        int depth = judge(root);
//         if(depth==-1)
//             return false;
//         else
//             return true;
        return depth != -1;
    }
    
    public int judge(TreeNode root){
        if(root==null)
            return 0;
        int left = judge(root.left);
        if(left==-1) return -1;
        int right = judge(root.right);
        if(right==-1) return -1;
        if(left-right>1 || right-left>1){
            return -1;
        } else {
            return 1 + (left>right?left:right);  // 括号不加就不对,因为运算优先级问题
        }
        
    }
}

(40) 数组中只出现一次的数字()

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
输入:[1,4,1,6]
返回值:[4,6]
思路:
方法1:借助HashMap,key为数组array中的元素,value为每个元素出现次数。最后遍历数组,根据key去查找哈希表,看哪个key的value是 1
方法2:借助ArrayList,list第一次遇见某元素则添加(list.contains(key)是false就添加),第二次遇见某元素则删除(list.remove(key);因为不需要的数字都出现了两次,所以不需要的数字都经历了先添加后删除,最后list中剩下的就是只出现了一次的想要的数字。
其实还可以稍微扩展下,比如有些数字出现了偶数次,另一些出现了奇数次,也可以灵活应用这个先添加后删除的方法。能节省下空间复杂度。

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param array int整型一维数组 
     * @return int整型一维数组
     */
    public int[] FindNumsAppearOnce (int[] array) {
        // write code here
//         HashMap<Integer, Integer> hash = new HashMap<>();
//         for(int i=0;i<array.length;i++){
//             if(hash.containsKey(array[i])){
//                 // 如果哈希表中没有,put()则创建并添加;已经有了put()就是更新
//                 hash.put(array[i], 1+hash.get(array[i]));
//             } else {
//                 hash.put(array[i], 1);
//             }
//         }
//         int [] res = new int[2];
//         int j=0;
//         for(int i=0;i<array.length;i++){
//             if(hash.get(array[i])==1){
//                 res[j++] = array[i];
//             }
//         }
//         if(res[0]>res[1]){
//             int tmp = res[0];
//             res[0] = res[1];
//             res[1] = tmp;
//         }
//         return res;
        
        // 第一次遇见就添加进去,第二次遇见就删除(所以这个方法适应范围还可以扩展)
        ArrayList<Integer> res = new ArrayList<>();
        for(int i=0;i<array.length;i++){
            if(res.contains(array[i])){
                res.remove((Integer)array[i]); // (Integer) (Object),要不会默认为索引
            } else {
                res.add(array[i]);
            }
        }
        // 就是想练习下怎么自定义比较规则;要不直接Collections.sort()
        res.sort(new Comparator<Integer>(){
            @Override
            public int compare(Integer o1, Integer o2){
                return o1.compareTo(o2);
            }
        });
        return new int[]{res.get(0), res.get(1)};
    }
}

注意事项:
list.remove(int index)
list.remove(Object obj)
在上面代码中,由于obj是int类型,所以list.remove(array[i])时会把array[i]默认为index从而报错,如果知道array[i]类型,可以(Integer) array[i],如果不知道类型可以 (Object) array[i],这个是肯定可以的。

不相关内容:

  1. 对于int型数组,求数组中每个数字出现次数。若数组中数字没有确定范围,可以用HashMap,键为数组中数字;若数组中数字有确切范围(0-n),可以用辅助数组res = new int[n],数组中某数字x出现一次就res[x]++;
  2. 对于字符串,字符数组。可以利用字符可以直接加减的特征,构建一个辅助数组;然后 c - 'A’当作数组下标, charArray[c - ‘A’] 存储字符 c 出现次数。

(41) 和为S的连续整数序列(穷举,dp----中等)

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
返回值描述:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
输入:9
返回值:[[2,3,4],[4,5]]
思路:采用双下标法(也叫双指针法)。首先根据x = (sum+1)>>1 求出最大的可能的序列头元素。然后 i 指向序列头, j 指向序列尾,并且i < j;同时让 i , j 在0~x范围内移动,就能穷举出所有可能。但是应该明白以 i 开头,或者以 j 结尾的序列只可能有一个,一旦找到一个满足要求的 i , j 就得移动。

import java.util.ArrayList;

public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        if(sum<3) // 1 + 2 最小都是3,所以小于3不可能有符合要求的
            return res;
        
        // 假设sum=100,(sum+1)/2是50,50+51大于100,所以50不可能是满足要求的序列的头;
        // 比50大的更不可能满足要求。sum是奇数也是这个道理
        int x = (sum+1)>>1;
        int [][] dp = new int[x][x];
        for(int i=0;i<x;i++){
            dp[i][i] = i+1;
        }
        // 因为dp[i][j]只与dp[i][j-1]有关,稍作思考就知道dp[i][j]完全可以省掉,用两个变量来代替.
        // 但是懒得去写这种更省空间的写法啦.
        for(int i=0; i<x; i++){
            for(int j=i+1; j<x; j++){
                dp[i][j] = dp[i][j-1] + j + 1;
                if(dp[i][j]==sum){
                    ArrayList<Integer> temp = new ArrayList<>();
                    for(int k=i; k<=j; k++){
                        temp.add(k+1);
                    }
                    res.add(temp);
                    // 以i+1开头的满足要求的序列只可能有一个,所以一旦找到一个就终止内部这个for循环
                    break;
                }
            }
        }
        return res;
    }
}

(42) 和为S的两个数字(双指针,双下标----中等)

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
返回值描述:对应每个测试案例,输出两个数,小的先输出。
输入:[1,2,4,7,11,15],15
返回值:[4,11]
思路:

  1. 首先明确一点,假如和为16,1x15 < 2x14 < 3x13 < … < 7x9 < 8x8
  2. 看到有序数组,利用下二分的双指针/双下标思想
  3. low, high 分别指向数组头尾。若arr[low] + arr[high]==sum,输出结果即可;若arr[low] + arr[high] < sum,low++;若arr[low] + arr[high] > sum,则high–;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        
        // 看到有序数组就想到二分,但是这题二分又解决不了问题.进而想到利用二分得双指针思想,分别
        // 指向数组的头尾
        ArrayList<Integer> res = new ArrayList<>();
        // 不论递增数组里有没有负数,对方法都没有影响
        if(array==null || array.length<2 || sum <array[0] || sum>=2*array[array.length-1]){
            return res;
        }
        int low = 0;
        int high = array.length - 1;
        int temp;
        while(low<high){
            temp = array[low] + array[high];
            if(temp == sum) {
                res.add(array[low]);
                res.add(array[high]);
                break;
            } else if(temp<sum){
                low++;
            } else {
                high--;
            }
        }
        return res;
    }
}

(43) 左旋转字符串(字符串----中等)

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
输入:“abcXYZdef”,3
返回值:“XYZdefabc”
思路:
没啥技巧,投机取巧得会用str.substrinf(int start, int end),左闭右开。或者先把字符串转换为字符数组,然后新建两个StringBuilder, 在sb.toString()。

public class Solution {
    public String LeftRotateString(String str,int n) {
        // 字符串为空,或者长度为0,或者左移位数<=0
        if(str==null || str.length()==0 || n<=0)
            return str;
        // n有可能比str.length()还大,所以需要取模
        int temp = n % str.length();
        if(temp==0)
            return str;
           // 正常方法
//         StringBuilder sb1 = new StringBuilder();
//         StringBuilder sb2 = new StringBuilder();
//         char [] s = str.toCharArray();
//         for(int i=0; i<s.length; i++){
//             if(i<temp){
//                 sb1.append(s[i]);
//             } else{
//                 sb2.append(s[i]);
//             }
//         }
//         return sb2.toString() + sb1.toString();
        
        // 投机取巧方法
        String left = str.substring(0, temp);
        String right = str.substring(temp, str.length());
        return right + left;
    }
}

相关内容:
String类常见操作:

  1. 字符串查找
    indexOf(String s) 返回字符串s在指定字符串中首次出现的位置
    lastIndexOf(String s) 返回字符串s在指定字符串中最后一次出现的位置
  2. 获取指定索引位置的字符
    str.charAt(index)
  3. 获取子字符串
    str.substring(index) 返回从指定索引位置开始截取直到该字符串结尾的字串
    str.substring(int start, int end) 截取中间片段,左闭右开
  4. 字符串替换
    str.replace(oldChar, newChar)
  5. 判断字符串的开始与结尾
    str.startsWith(String prefix) 判断当前字符串对象的前缀是否是参数指定的字符串,返回boolean
    str.endsWith(String postfix) 判断当前字符串对象否以参数指定的字符串结尾,返回boolean
  6. 判断字符串是否相等
    str1.equals(String str2) 区分大小写;字符和长度相等时返回true
    str1.equalsIgnoreCase(String str2) 不区分大小写
    把两个字符串装入ArrayList, 用Collections.sort()可以把字符串按照字典序排序
  7. 字母大小写转换
    str.toLowerCase()
    str.toUpperCase()
  8. 字符串分割
    str.split(String sign)
    str.split(String sign, int limit) 限定分割次数
    想定义多个分隔符,可使用 “|”。例如 “,|=” 表示分隔符分别为 “,” 和 “=” 。

(44) 翻转单词顺序列(字符串,栈----较难)

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
输入:“nowcoder. a am I”
返回值:“I am a nowcoder.”
思路:和颠倒顺序相关的,可以考虑用栈操作

import java.util.Stack;

public class Solution {
    public String ReverseSentence(String str) {
        if(str==null || str.length()==0)
            return str;
        
        // 遇到空格就把空格添加进新的字符串,不入栈;遇到单词就让滑窗扩大,直到空格,
        // 使用stack可以更方便地把倒序的单词变成正顺序.
        // 而且一旦提到倒叙,自然而然想到利用stack
        char [] strArr = str.toCharArray();
        int i = strArr.length - 1;
        StringBuilder sb = new StringBuilder();
        Stack<Character> s = new Stack<>();
        
        // 字符数组从右向左遍历
        while(i>=0){
            // s.clear(); // 只要下面代码没问题,这句可有可无
            
            // 把一个完整的单词入栈
            while(i>=0 && strArr[i]!=' '){
                s.push(strArr[i]);
                i--;
            }
            // 完整单词再出栈
            while(!s.isEmpty()){
                sb.append(s.pop());
            }
            // 遇到空格则直接添加进StringBuilder中
            if(i>=0){
                sb.append(strArr[i]);
                i--;
            }
            
        }
        return sb.toString();
    }
}

相关内容:

  1. “nowcoder. a am I” 颠倒成 “I am a nowcoder.” 这种颠倒单词顺序的。或者 “I am” 倒成"ma I"这种完全颠倒的,都是利用栈进行控制。
  2. 具体的控制方式,比如原数组的遍历方式(左→右,右←左);修改遍历时循环内部的控制逻辑。

(45) 扑克牌的顺子(数组----中等)

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
输入:[0,3,2,6,4]
返回值:true
思路:
这道题就是看能不能相通逻辑,怎么去做,怎么使用特殊的数据结构和方法。

  1. 先给数组排序,并计算0(大小王)的个数
  2. 0除外,挨个计算相邻两数numbers[i+1]和numbers[i]之间差几个数。比如numbers[i+1]和numbers[i]分别为8,7时,差值为 1 ;求出差值的和;
  3. 若0的个数小于差值的和,说明大小王补空位时不够用,不是顺子
  4. 还要判断有没有除0外相同的牌,有相同的牌直接就不是顺子了
import java.util.Collections;
import java.util.Arrays;

public class Solution {
    public boolean IsContinuous(int [] numbers) {

        if(numbers==null || numbers.length<=4)
            return false;
        Arrays.sort(numbers); // 排序
        int cnt = 0; // 计算大小王的个数
        for(int temp:numbers){
            if(temp==0)
                cnt++;
        }
        int sum=0; //统计除了0之外的数,相邻两个数之间的差值之和
        for(int i=cnt;i<numbers.length-1; i++){
            if(numbers[i+1]==numbers[i]) // 除0外,相邻两数若相等,则不是顺子
                return false;
            else
                sum += numbers[i+1]-numbers[i] - 1; //统计相邻两数间的差值
        }
        if(cnt>=sum)
            return true;
        else
            return false;
        
    }
}

相关内容:

  1. import java.util.Collections; 只用于 ArrayList,可以自定义比较规则;不定义时默认 升序/小根堆 可以直接用。Collections.sort(list);
    import java.util.Arrays; 只用于数组,默认是升序排列,Arrays.sort(int[] a);也可以对数组部分排序Arrays.sort(int[] a, int start, int end),左闭右开;也可以指定比较规则,指定方式和Collections.sort()以及list.sort()的指定方式一样。
    Arrays.sort(), Collections.sort()都有默认比较方式,list.sort()必须指定比较规则后才能用。
  2. 数组长度arr.length;字符串长度str.length();其他可变数组,栈 list.size(), stack.size()
  3. StringBuilder有四种构造方法:
    StringBuilder() 空方法
    StringBuilder(int capacity) 指定初始容量
    StringBuilder(String str) 根据字符串建立sb
    StringBuilder(char[] seq) 根据字符数组建立sb
  4. 字符串构造方法包括这几种,new String();new String(char[] seq);String str = “”;

(46) 孩子们的游戏,圆圈中最后剩下的数(arraylist,数学规律----中等)

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1
输入:5,3
返回值:3
思路:
一种是找数学规律,太麻烦。我们就用arraylist和while循环来模拟这个游戏即可。

import java.util.ArrayList;

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n==0 || m==0)
            return -1;
        // 用list模拟孩子组成的圈
        ArrayList<Integer> list = new ArrayList<>();
        for(int i=0;i<n;i++)
            list.add(i);
        
        // 喊0的小孩子的位置;刚开始游戏时喊0的是下标为0的孩子
        int cur = 0;
        
        while(list.size()>1){
            // 小孩子开始从0数到m啦
            for(int i=1; i<m; i++){
                cur++;
                if(cur==list.size()){
                    cur=0; // 数到圈尾了,重置cur为圈首
                }
            }
            list.remove(cur);
            // 万一删除的是最后一个,得重置cur为圈首
            if(cur==list.size()){
                cur=0;
            }
        }
        return list.get(0);
    }
}

(47) 求1+2+3+…+n(数学,语法替代----中等)

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
输入:5
返回值:15
思路:
不能使用乘除法,那么只能用加法了。可是也不让用循环,那只能用递归来代替循环(反过来,循环也能替换递归;就像dp问题中的从下到上时的dp数组和从上到下的记忆化搜索)。可是递归需要缩小规模,这个可以用加减来实现。那递归终止条件呢?if-else,switch-case,条件判断语句不让用了,可以用逻辑表达式 && || !来代替判断语句。

在本题中就是:递归代替循环,逻辑与代替条件判断。
if(A) {B;}被替换后就是 A && B。前面的意思是如果满足A则执行B,A不满足就不执行B。后面的意思是A成立再执行或者判断B,A若不成立B也就没必要再执行判断了。所以 if 和 && 是等价替换的。

A && B 中A一旦不成立也就不再执行B的这种现象叫做与操作的短路特性。

public class Solution {
    public int Sum_Solution(int n) {
        
        int sum = n;
        boolean flag = (sum>0) && ((sum+=Sum_Solution(--n))>0);
        return sum;
    }
}

(48) 不用加减乘除做加法(数学,位操作----中等)

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
输入:1,2
返回值:3
思路:

  1. 两个数异或:相当于用二进制表达时每一位相加,而不考虑进位
  2. 两个数相与,并左移一位:相当于两个数相加时产生的进位
  3. 进位不为0时重复上述步骤;当进位为0时,两个数异或的值就是这两个数的和(代码如下)
public class Solution {
    public int Add(int num1,int num2) {
        
        while(num2!=0){ 
            int num = (num1^num2); // 不考虑进位时的和
            int carray = (num1&num2)<<1; // 进位
            num1 = num; // 和
            num2 = carray; // 进位
        }
        return num1;
    }
}

别人的分析如下:
在这里插入图片描述

(49) 把字符串转换为整数(字符,字符串----困难)

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

输入描述:输入一个字符串,包括数字字母符号,可以为空
返回值描述:如果是合法的数值表达则返回该数字,否则返回0
示例1:"+2147483647" 返回值:2147483647
示例2:“1a33” 返回值: 0
思路:
我们知道只有 “+1377”,"-3843",“4364” 这种数据才能转换成整数,其他的都有问题。如果在写代码时候,把所有有问题的情况都考虑到,然后通过逻辑控制避开这些情况,那有时候太难啦。所以既然知道了哪几种情况才是正确的,那我们就以这几种正确情况为例子进行分析,写出一套适合正确例子的逻辑语句,正确的测试用例来了就能顺利通过我们的逻辑语句,其他所有通不过我们的语法逻辑的统统归类到other,也就是错误情况。

import java.util.ArrayList;

public class Solution {
    public int StrToInt(String str) {
        
//         return Integer.parseInt(str);
        if(str==null || str.length()==0)
            return 0;
        char [] strArr = str.toCharArray();
        int res = 0;
        // 核心思想就是:错的情况五花八门,错综复杂,我都不考虑.我只考虑对的情况,
        // 并且在假设是对的情况下写代码,只要不满足我假设的情况就是other,就是return 0;
        // 只在最后进行一个符号位的判断(能进行到这一步说明前面没有return,假设对的情况成立啦)
        for(int i=0; i<strArr.length; i++){
            if(i==0 && (strArr[i]=='+' || strArr[i]=='-')){
                continue;
            } else if(strArr[i]>='0' && strArr[i]<='9'){
                res = res*10+strArr[i]-'0';
            } else {
                return 0;
            }
        }
        
        if(strArr[0]=='+' || (strArr[0]>='1' && strArr[0]<='9'))
            return res;
        else if(strArr[0]=='-')
            return res*(-1);
        
        return res;
    } 
}

(50) 数组中重复的数字(数组----简单)

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中第一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是第一个重复的数字2。没有重复的数字返回-1。
输入:[2,3,1,0,2,5,3]
返回值:2
思路:见前面(40)题的最后

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param numbers int整型一维数组 
     * @return int整型
     */
    public int duplicate (int[] numbers) {
        // write code here
        int len = numbers.length;

		// 动态方式定义int类型数组时,数组中每个值都被初始化为0
        int [] cnt = new int[len];
        for(int i=0; i<len;i++){
            if(++cnt[numbers[i]] > 1){
                return numbers[i];
            }
        }
        return -1;
    }
}

相关内容:
动态方式定义int类型数组时,数组中每个值都被初始化为0;

不相关内容:HashSet的基本使用

  1. HashSet基于HashMap实现的,是一个不允许有重复元素的集合。但HashSet允许有null值。HashSet是无序的,不会记录插入顺序。
  2. import java.util.HashSet;
    HashSet<引用类型> set = new HashSet<>();
  3. HashSet的用法和ArrayList几乎一样
    set.add(obj)
    set.remove(obj) , ArrayList中可以有索引和obj,HashSet只能是obj
    set.clear()
    set.contains(obj)
    set.size()
    for-each遍历HashSet,for(引用类型 obj :set){}

什么叫记忆化搜索?
记忆化搜索就是DFS基础上增加四行代码。
(1) 定义保存中间及最终结果的数组,列表
(2)给数组或列表初始化
(3)递归函数中,进行计算前先查询一下数组或列表中是否有所需要的,有则停止接着向下递归
(4)若第(3)中查询不到,则接着向下递归,等递归结束有了答案之后将中间/最终结果写入数组或列表中存储起来。

(51) 构建乘积数组(数组,把头尾处理一般化----中等)

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0] x A[1] x … x A[i-1] x A[i+1] x … x [n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。
输入:[1,2,3,4,5]
返回值:[120,60,40,30,24]
思路:建立两个数组,分别命名为left,right;left[i] 代表 A[0] x A[1] x … x A[i-1],right[i] 代表 A[i+1] x … x [n-1]。然后 B[i] = left[i] x right[i]。
小技巧:为了能够把数组的首尾,或者链表的头尾当作中间节点一样,来统一操作所有的数组下标或者链表节点,省去对链表头尾节点,数组首尾下标进行特殊处理的麻烦;可以给链表新建一个头节点,给链表虚拟出一对头尾节点,再或者给数组增加一个虚拟的常数 1 / 0 ,通过这些方式使得对整个链表和数组的处理可以一般化。
在这里插入图片描述

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        if(A==null || A.length<=1){
            return null;
        }
        int len = A.length;
        int [] left = new int[len];
        int [] right = new int[len];
        left[0]=1;
        for(int i=1;i<len;i++){
            left[i]=left[i-1]*A[i-1];
        }
        right[len-1]=1;
        for(int i=len-2;i>=0;i--){
            right[i]=A[i+1]*right[i+1];
        }
        int [] B = new int[len];
        for(int i=0; i<len; i++){
            B[i]=left[i]*right[i];
        }
        return B;
    }
}

(54) 字符流中第一个不重复的字符(字符----中等)

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
返回值描述:如果当前字符流没有存在出现一次的字符,返回#字符。
输入:无
返回值:无
思路:多看两遍代码就没啥大问题。注意三点:

  1. StringBuilder其实就是一个可以不需要创建新字符串就能进行字符串修改的动态字符串。除了有sb.append(字符/字符串)方法外,字符串有的 charAt(idnex) 和 .length() 方法 sb 也都有;
  2. ASCII码表有256个字符,其中128个可打印字符,也就是可见的。这些可打印字符中,算法刷题过程中可能涉及到的ascii码值最小的就是 ‘!’。常用字符的ascii码值:‘0’ :48; ‘A’ :65;‘a’:97;
  3. 字符是基本类型,可以直接加减,用不同字符ascii值的差距作为数组下标。
public class Solution {
    
    private StringBuilder sb = new StringBuilder(); // 全局变量
    private int [] cnt = new int[128]; // 全局变量
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        sb.append(ch); // sb可以添加字符和字符串
        cnt[ch - '!']++;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(int i=0; i<sb.length(); i++){
            if(cnt[sb.charAt(i)-'!']==1){
                return sb.charAt(i);
            }
        }
        return '#';
    }
}

(55) 链表中环的入口(双指针,快慢指针----中等)

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
输入:无
返回值:无
思路:

设置快慢指针,都从链表头出发,快指针每次走两步,慢指针一次走一步,假如有环,一定相遇于环中某点(结论1)。接着让两个指针分别从相遇点和链表头出发,两者都改为每次走一步,最终相遇于换入口(结论2)。

两个结论:

  1. 设置快慢指针,假如有环,一定会相遇;
  2. 两个指针分别从链表头和相遇点继续出发,每次走一步,最后一定相遇于环入口。

结论1证明:设置快慢指针fast和low,fast每次走两步,low每次走一步。假如有环,两者一定会相遇(因为low一旦进环,可看作fast在后面追赶low的过程,每次两者都接近一步,最后一定能追上)。
结论2证明:不贴了,贴出来看了也记不住。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        if(pHead==null || pHead.next==null)
            return null;
        
        ListNode p1 = pHead;
        ListNode p2 = pHead;
        while(p1!=null && p2!=null){
            p1=p1.next.next;
            p2=p2.next;
            if(p1==p2){
                p1=pHead;
                while(p1!=p2){
                    p1=p1.next;
                    p2=p2.next;
                }
                return p1;
            }
        }
        return null;
    }
}

(56) 删除链表中重复的节点(HashSet,链表头处理的一般化----较难)

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
输入:{1,2,3,3,4,4,5}
返回值:{1,2,5}
思路:
方法1:遍历一次链表,用一个HashSet存储重复的值。第二次遍历链表,根据判断链表头中的值是否在HashSet中来更新链表头,然后设置两个指针向右移动把重复的节点去掉。
方法2:在链表的头节点前面再新建一个节点当作新的头节点,好处是可以把旧的头节点当作普通节点来一视同仁,方便后续处理。此时利用两个指针进行遍历即可。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        
        if(pHead==null || pHead.next==null)
            return pHead;
        ListNode head = new ListNode(0); //好处是可以把头节点当作普通节点来一视同仁,方便后续处理
        head.next = pHead;
        ListNode slow=head, fast=head.next;
        while(fast!=null && fast.next!=null){
            if(fast.next.val==fast.val){
                while(fast.next!=null && fast.next.val==fast.val){
                    fast=fast.next;
                }
                slow.next=fast.next;
                fast=fast.next;
            } else {
                slow = slow.next;
                fast=fast.next;
            }
        }
        return head.next;
        
//         我的蠢方法
//         if(pHead==null)
//             return pHead;
//         ListNode p = pHead;
//         HashSet<Integer> set = new HashSet<>();
        
//         发现重复的数字,就把数字加入到不重复的Set集合中
//         while(p!=null){
//             if(p.next!=null && p.next.val==p.val){
//                 set.add(p.val);
//             }
//             p = p.next;
//         }
//         
//         头节点有可能就是重复的,如果头节点是重复节点则更新头节点,用pre表示最新的头节点
//         ListNode pre = pHead;
//         while(pre!=null && set.contains(pre.val)){
//             pre=pre.next;
//         }
//         
//         假如整个链表都是重复的,经过上面的更新头节点操作后,新的头节点pre有可能是null
//         if(pre==null) 
//             return null;
//         
//         ListNode first = pre; // 用first保存头节点
//         p = pre.next;
//         while(p!=null){
//             if(set.contains(p.val)){
//                 pre.next = p.next;
//                 p=p.next;
//                 continue;
//             }
//             pre=p;
//             p=p.next;
//         }
//         return first;
    }
}

(57) 二叉树的下一个节点(中序遍历时找下一个节点----中等)

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
输入:无
返回值:无
思路:
先看pNode节点有没有右子树,有的话,下一个就是右子树左下角那个。如果pNode没有右子树,这个时候分情况讨论。如果pNode父亲非空,且pNode是自己父亲的左孩子,那它的父亲就还没访问过,它父亲就是下一个。如果pNode是自己父亲的右子树,那它父亲就比pNode更早被访问,它父亲就不是下一个;此时把pNode的父亲赋值给pNode,再接着判断新PNode是自己父亲的左孩子还是右孩子。

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        // 先看pNode节点有没有右子树,有的话,下一个就是右子树左下角那个
        if(pNode.right!=null){
            TreeLinkNode p = pNode.right;
            while(p.left!=null){
                p=p.left;
            }
            return p;
        }
        
        // 如果pNode没有右子树,这个时候分情况讨论。如果pNode父亲非空,且pNode是自己父亲的左孩子,那它的
        // 父亲就还没访问过,它父亲就是下一个。如果pNode是自己父亲的右子树,那它父亲就比pNode更早被访问,
        // 它父亲就不是下一个;此时把pNode的父亲赋值给pNode,再接着判断新PNode是自己父亲的左孩子还是右孩子
        while(pNode.next!=null){
            if(pNode.next.left==pNode){
                return pNode.next;
            }
            pNode=pNode.next;
        }
        // 没有下一个,返回null
        return null;
    }
}

相关内容:中序遍历时,求某节点的上一个节点?
和上题稍微变化一下。先看pNode节点有没有左子树,有的话,上一个就是左子树右下角那个。如果pNode没有左子树,这个时候分情况讨论。如果pNode父亲非空,且pNode是自己父亲的右孩子,那它的父亲已经访问过,它父亲就是上一个。如果pNode是自己父亲的左子树,那它父亲就比pNode更迟被访问,它父亲就不是上一个;此时把pNode的父亲赋值给pNode,再接着判断新PNode是自己父亲的左孩子还是右孩子。

(58) 对称的二叉树(双节点作为参数递归,栈,队列----困难)

请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
输入:{8,6,6,5,7,7,5},{8,6,9,5,7,7,5}
返回值:true, false
思路:
方法1:把根节点的左子树和右子树作为两个参数传入递归函数;judge(pRoot.left, pRoot.right)。树的递归更多都是传一个节点,形成定式思维啦,同时传俩参数轻松解决。
方法2:用栈或者队列。左右子节点,成对的入栈,成对的出栈并判断。或者左右两个子节点同时的入队,同时的出队。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.*;

public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
//         // 数组递归时候知道传好几个参数?到树的递归就只会传一个节点了?树的递归也可以把多个节点当参数进行传递啊
//         if(pRoot==null) 
//             return true;
//         return judge(pRoot.left, pRoot.right);
        
        
        // 非递归方法1:用栈,成对的入栈,成对的出栈并判断
//         if(pRoot==null)
//             return true;
//         Stack<TreeNode> stack = new Stack<>();
//         stack.push(pRoot.left);
//         stack.push(pRoot.right);
//         while(!stack.isEmpty()){
//             TreeNode right = stack.pop();
//             TreeNode left = stack.pop();
//             if(right==null && left==null) continue;
//             if(right==null || left==null) return false;
//             if(left.val!=right.val) return false;
//             stack.push(left.left);
//             stack.push(right.right);
//             stack.push(left.right);
//             stack.push(right.left);
//         }
//         return true;
        
        
        // 非递归方法2:用队列,成对的入队,成对的出队并且判断
        if(pRoot==null)
            return true;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot.left);
        queue.offer(pRoot.right);
        while(!queue.isEmpty()){
            TreeNode left = queue.poll();
            TreeNode right = queue.poll();
            if(left==null && right==null) continue;
            if(left==null || right==null) return false;
            if(left.val!=right.val) return false;
            queue.offer(left.left);
            queue.offer(right.right);
            queue.offer(left.right);
            queue.offer(right.left);
        }
        return true;
        
        
    }
    
    private boolean judge(TreeNode left, TreeNode right){
        if(left==null && right==null)
            return true;
        if(left==null || right==null)
            return false;
        if(left.val==right.val){
            return judge(left.left, right.right) && judge(left.right, right.left);
        } else{
            return false;
        }
    }
}

(59) 按之字形打印二叉树(队列,栈,树----较难)

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
输入:{8,6,10,5,7,9,11}
返回值:[[8],[10,6],[5,7,9,11]]
思路:
方法1:使用队列进行层次遍历。关键有三点:

  1. 怎么分层,怎么确定队列中的每个节点属于树的哪一层,哪些节点是同一层的怎么判断?
  2. 即使知道了怎么分层,怎么确定这层应该正着存放数据还是倒着存放?
  3. 知道哪一层应该倒着放数据后,怎么实现反着存放数据?

通过 int size = queue.size() 来确定分层;设立一个标志 boolean flag 确定哪一层应该怎么放;通过可变数组的 add(index, obj)方法实现反着存放数据 list.add(0, obj)。

方法2:涉及到颠倒顺序,可用一个,两个栈实现。具体本题中,则需要设置两个栈,分别存放不同层的节点,两个栈分别实现不同的入栈和出栈操作,这样就能实现不同层交替正反顺序存放数据;同时由于两个栈分别存放的是不同层的节点,也就自动实现了分层。具体怎么写代码,多看两遍下面就可以。

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
import java.util.Stack;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        // 用队列实现(用q.size()实现分层,用list.add(0, val)实现反序存放,用flag控制每一层是正放还是反放)
        ArrayList<ArrayList<Integer>> list = new ArrayList<>();
        if(pRoot==null){
            return list;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        ArrayList<Integer> temp = new ArrayList<>();
        boolean flag = true;
        queue.offer(pRoot);
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i=0; i< size; i++){
                TreeNode p = queue.poll();
                if(flag){
                    temp.add(p.val);
                } else{
                    temp.add(0, p.val);
                }
                if(p.left!=null) queue.offer(p.left);
                if(p.right!=null) queue.offer(p.right);
            }
            list.add(new ArrayList<>(temp));
            temp.clear();
            flag = !flag;
        }
        return list;
        
        
//         涉及到颠倒顺序,可以考虑用栈实现(怎么分层,怎么每层顺序相反)。
//         循环操作两个栈实现交替正反序存放以及分层
//         ArrayList<ArrayList<Integer>> list = new ArrayList<>();
//         if(pRoot==null){
//             return list;
//         }
//         Stack<TreeNode> s1 = new Stack<>();
//         Stack<TreeNode> s2 = new Stack<>();
//         s1.push(pRoot);
//         ArrayList<Integer> temp = new ArrayList<>();
//         while(!s1.isEmpty() || !s2.isEmpty()){
//             if(!s1.isEmpty()){
//                 while(!s1.isEmpty()){
//                     TreeNode p = s1.pop();
//                     temp.add(p.val);
//                     if(p.left!=null) s2.push(p.left);
//                     if(p.right!=null) s2.push(p.right);
//                 }
//                 list.add(new ArrayList<>(temp));
//                 temp.clear();
//             } else{
//                 while(!s2.isEmpty()){
//                     TreeNode p = s2.pop();
//                     temp.add(p.val);
//                     if(p.right!=null) s1.push(p.right);
//                     if(p.left!=null) s1.push(p.left);
//                 }
//                 list.add(new ArrayList<>(temp));
//                 temp.clear();
//             }
//         }
//         return list;
    }

}

注意下 list.add(new ArrayList<>(temp)); temp.clear();操作就行。

(60) 把二叉树打印成多行(队列,层次遍历----中等)

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
输入:{8,6,10,5,7,9,11}
返回值:[[8],[6,10],[5,7,9,11]]
思路:看下(59)题的方法1,就知道怎么在层次遍历过程中对节点进行分层。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer> > list = new ArrayList<>();
        if(pRoot==null)
            return list;
        ArrayList<Integer> temp = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i=0; i<size; i++){
                TreeNode p = queue.poll();
                temp.add(p.val);
                if(p.left!=null) queue.offer(p.left);
                if(p.right!=null) queue.offer(p.right);
            }
            list.add(new ArrayList<>(temp));
            temp.clear();
        }
        return list;
    }
    
}

(61) 序列化二叉树(队列,树----较难)

请实现两个函数,分别用来序列化和反序列化二叉树。

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树。

输入:{8,6,10,5,7,9,11}
返回值:{8,6,10,5,7,9,11}

思路:序列化时用队列进行层次遍历即可。反序列化时分为几步:首先通过结点值结束标识符 ! 对字符串进行分割;其次遍历分割后产生的字符串数组,重建各个结点,得到一个节点数组;最后遍历节点数组,通过一个巧妙的双下标方法重构出二叉树。这个双下标方法是本题的精髓。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.*;

public class Solution {
    String Serialize(TreeNode root) {
        if(root==null){
            return "#!";
        }
        StringBuilder sb = new StringBuilder();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            // java中, 队列,栈,list都能添加null。
            // 因为下面offer语句执行前没有进行判断,所以从栈中弹出后要判断是不是null
            if(node!=null){
                sb.append(node.val + "!");
                queue.offer(node.left);
                queue.offer(node.right);
            } else {
                sb.append("#!");
            }
        }
        return sb.toString();
  }
    TreeNode Deserialize(String str) {
        TreeNode head = null;
        if(str==null || str.length()==0)
            return head;
        String [] nodes = str.split("!");
        TreeNode[] treeNodes = new TreeNode[nodes.length]; // 数组每个元素默认为null
        for(int i=0; i< nodes.length; i++){
            if(!nodes[i].equals("#")){ // 不能用 ==
//                 Integer.parseInt(nodes[i]);Integer.valueOf(nodes[i])
                treeNodes[i] = new TreeNode(Integer.parseInt(nodes[i]));
            }
        }
        // i 指向父节点,j 指向子节点(这个妙啊)
        for(int i=0, j=1; j<treeNodes.length; i++){
            if(treeNodes[i]!=null){
                treeNodes[i].left = treeNodes[j++];
                treeNodes[i].right = treeNodes[j++];
            }
        }
        return treeNodes[0];
  }
    
}

(62) 二叉搜索树的第k个节点(树----简单)

给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。
输入:{5,3,7,2,4,6,8},3
返回值:{4}
说明:按结点数值大小顺序第三小结点的值为4
思路:中序遍历。另外会定义和使用全局变量也能有很大方便。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    
    private TreeNode target = null; // 全局的引用类型对象
    private int k1=0; // 全局的基本类型对象,全局使用
    
    TreeNode KthNode(TreeNode pRoot, int k) {
        k1=k;
        if(pRoot==null || k<=0)
            return null;
        getKthNode(pRoot);
        return target;
    }

    public void getKthNode(TreeNode root){
        if(root==null || k1<=0) return ;
        getKthNode(root.left);
        k1--;
        if(k1==0){
            target = root;
            return; // 这句话没有的话就是多执行一些没必要的查找,但不会影响最终返回结果
        }
        getKthNode(root.right);
    }

}

(63) 数据流的中位数(排序,数组----中等)

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
输入:无
返回值:无
思路:直接看代码

import java.util.*;

public class Solution {
    
    ArrayList<Integer> temp = new ArrayList<>();
    
    public void Insert(Integer num) {
        
        // 投机取巧方法
//         temp.add(num);
//         Collections.sort(temp);
        
        // 自己排序的方法
        int index = findIndexToInsert(num);
        temp.add(index, num);
        // list.add(index, obj)  list是没有insert方法的
    }

    public int findIndexToInsert(int num){
        if(temp==null || temp.size()==0)
            return 0;
        
        for(int i=0; i<temp.size(); i++){
            if(temp.get(i)>num)
                return i;
        }
        return temp.size();
    }
    
    
    public Double GetMedian() {
        
        if(temp==null || temp.size()==0){
            return 0.0;
        }
        
        int index = temp.size()/2;
        
        if(temp.size()%2==1){
            return (double) temp.get(index);  // 不要写成 (Double)
        } else {
            return (temp.get(index)+temp.get(index-1)) / 2.0;
        }
    }


}

(64) 滑动窗口的最大值(双端队列,滑动窗口----较难)

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
窗口大于数组长度的时候,返回空。
输入:[2,3,4,2,6,2,5,1],3
返回值:[4,4,6,6,6,5]
思路:滑动窗口应当是队列,但为了得到滑动窗口的最大值,队列序是可以从两端删除元素,因此使用双端队列。
原则:
对新来的元素k,将其与双端队列中的元素相比较
1. 前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值啦),
2. 前面比k大的x,比较两者下标,判断x是否已经不在窗口之内,不在了,直接移出队列
队列的第一个元素是滑动窗口中的最大值。另外:因为我们需要根据下标比较前面比k的x是否已经不在窗口之内了,所以双端队列中实际添加的是元素对应的下标,而不是元素值。

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if(num==null || num.length==0 || size<=0 || size > num.length){
            return res;
        }
        
        // 蠢方法,面试估计不给分的
//         for(int i=size-1; i<num.length; i++){
//             int max=Integer.MIN_VALUE;
//             for(int j=i; j>=i-size+1;j--){
//                 max=max>num[j]?max:num[j];
//             }
//             res.add(max);
//         }
//         return res;
        
        
        // 下面用滑动窗口的方式
        // 这个for循环中 i < size-1 说明这个循环只考虑和比较第一个滑动窗口中最后一个元素之前的所有其他元素;
        // 这样在下面的第二个for循环中才能做到移动一步就是一个窗口。
        LinkedList<Integer> indexDeque = new LinkedList<>();
        for(int i=0; i<size-1; i++){
        	// 挨个和队列的屁股元素比较大小,比屁股元素大,就删除屁股元素,比屁股元素小或者队列为空,就添加这个元素
            while(!indexDeque.isEmpty() && num[i]>num[indexDeque.peekLast()]){
                indexDeque.pollLast();
            }
            indexDeque.addLast(i);
        }
        
        for(int i=size-1; i<num.length; i++){
        	// 下面几行和上面第一个for循环一样
            while(!indexDeque.isEmpty() && num[i]>num[indexDeque.peekLast()]){
                indexDeque.pollLast();
            }
            indexDeque.offerLast(i);
            
            if(i-indexDeque.peekFirst()>=size){ 
                // 因为循环中上一步处理过后,都是没过期的元素;循环中这次向右移动一步,要过期也只可能有一个过期,
                // 所以不用for循环删除过期的,只可能有一个过期的,如果这一个过期了删除即可。
                indexDeque.pollFirst();
            }
            res.add(num[indexDeque.peekFirst()]);
        }
        return res;
    }
}

相关内容:

  1. 双端队列 LinkedList<类型> deque = new LinkedList<>();
  2. deque.offerLast() ; deque.offerFirst(); deque.peekLast(); deque.peekFirst(); deque.pollLast(); deque.pollFirst(); deque.size(); deque.isEmpty() 这些都是常用方法。

不相关内容:list.remove((Character) ch) 或者 list.remove((Object) ch) 这样做的目的是防止可变数组把字符的ascii码当作index。因为list支持按索引删除和按对象值删除,而且是默认按索引删除的。

(65) 矩阵中的路径(dfs,回溯----较难)

题目描述见下图:
在这里插入图片描述
输入:“ABCESFCSADEE”,3,4,“ABCCED”
返回值:true

输入:“ABCESFCSADEE”,3,4,“ABCB”
返回值:false

思路:把矩阵中每个元素当作起始点进行一次dfs。dfs具体怎么写看一眼代码就恍然大悟。

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param matrix string字符串 
     * @param rows int整型 
     * @param cols int整型 
     * @param str string字符串 
     * @return bool布尔型
     */
    public boolean hasPath (String matrix, int rows, int cols, String str) {
        // write code here
        char [][] arr = new char[rows][cols];
        for(int i=0; i<rows; i++){
            for(int j=0; j<cols; j++){
                arr[i][j] = matrix.charAt(i*cols+j);
            }
        }
        char [] s = str.toCharArray();
       
        for(int i=0; i<rows; i++){
            for(int j=0; j<cols; j++){
                int [][] dp = new int[rows][cols]; //默认都是0
                int k=0; // 代表目前已经成功匹配到字符串中第几个元素啦
                boolean res = judge(i, j, k, dp, arr, s); // 每次只变i,j两项,dp,arr,s,k都固定的
                if(res){
                    return res;
                }
            }
        }
        return false;
    }
    
    public boolean judge(int i, int j, int k, int [][] dp, char [][] arr, char [] s){
        if(i<0 || i>=arr.length || j<0 || j>=arr[0].length || arr[i][j]!=s[k] || dp[i][j]==1){
        // 要么i,j超出范围;或者没超出范围但是对应位置元素和字符串中接下来要比较的字符不一样;
        // 再或者字符也一样,但是这个位置已经访问过了
            return false;
        }
        dp[i][j]=1; // 这个位置标记为访问过了
        k++; // 又比较成功一个元素
        if(k==s.length) return true; 
        return judge(i-1, j, k, dp, arr, s) 
            || judge(i+1, j, k, dp, arr, s) 
            || judge(i, j-1, k, dp, arr, s) 
            || judge(i, j+1, k, dp, arr, s);
    }
}

(66) 机器人的运动范围(dfs,回溯----较难)

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
输入:5,10,10
返回值:21
思路:看代码就行。和上一题很像。

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        int [][] dp = new int [rows][cols]; // 元素默认值都为0
        return get(0, 0, rows, cols, dp, threshold);
    }
    
    public int get(int i, int j, int rows, int cols, int [][] dp, int threshold){
        if(i<0 || i>= rows || j<0 || j>=cols || numSum(i)+numSum(j)>threshold || dp[i][j]==1){
        // 要么下标越界,要么不越界但是不能进入,要么能进入但是访问过了
            return 0;
        }
        dp[i][j]=1; // 标记为已访问
        return get(i+1, j, rows, cols, dp, threshold)
            + get(i-1, j, rows, cols, dp, threshold)
            + get(i, j+1, rows, cols, dp, threshold)
            + get(i, j-1, rows, cols, dp, threshold)
            + 1;
    }
    
    public int numSum(int num){
        int res=0;
        while(num!=0){
            res+= num%10;
            num/=10;
        }
        return res;
    }
}

(67) 剪绳子(数学----中等)

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入描述:输入一个数n,意义见题面。(2 <= n <= 60)
返回值描述:输出答案
输入:8
返回值:18
思路:当 n 大于 3 时,将 n 尽可能地分割为3的和时,乘积最大。大于 3 时有三种情况;余数为 0 ;余数为 1 ;余数为 2 。
余数为 0 时:正好分出来的都是 3 ,所有 3 相乘;
余数为 1 时:分出来的最后两个数是 3 1;假设前面所有数相乘是 A,那么 A x (3 + 1) > A x 3 x 1
余数为 2 时:分出来的最后两个数是 3 2;假设前面所有数相乘是 A,那么 A x (3 + 2) < A x 3 x 2

public class Solution {
    public int cutRope(int target) {
        if(target==2)
            return 1;
        if(target==3)
            return 2;
        if(target%3==0){
            return (int)Math.pow(3, target/3);
        } else if(target%3==1){
            return 4*(int)Math.pow(3, target/3 - 1);
        } else {
            return 2*(int)Math.pow(3, target/3);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值