leetCode心得

leetCode刷题记录

  • 题目列表
    • 剑指 Offer 09. 用两个栈实现队列
      • 题解思路
      • 代码
      • 进阶学习
      • 运行结果
    • 面试题 02.03. 删除中间节点
      • 题解思路
      • 代码
      • 运行结果
      • 思考
    • 剑指 Offer 29. 顺时针打印矩阵
      • 题解思路
      • 代码
      • 运行结果
    • 54. 螺旋矩阵
      • 题解思路
      • 代码
      • 运行结果
      • 小结:
      • 较好的解法(转载)--通过缩短边界范围
    • 718. 最长重复子数组
      • 题解思路
      • 代码
      • 运行结果
      • 较好的解法
    • 剑指 Offer 10- II. 青蛙跳台阶问题
      • 题解思路
      • 代码
      • 运行结果![在这里插入图片描述](https://img-blog.csdnimg.cn/20200701104750353.png)
      • 小结
    • 面试题 08.01. 三步问题
      • 题解思路
      • 代码
    • 14. 最长公共前缀
      • 题解思路
      • 代码
    • 61. 旋转链表
      • 题解思路
      • 代码
      • 运行结果
    • 8. 字符串转换整数 (atoi)
      • 题解思路
      • 代码
      • 比较容易理解的做法
      • 不明觉历的做法--有限状态机
    • 378. 有序矩阵中第K小的元素
      • 题解思路
      • 代码
      • 另一种思路
    • 7. 整数反转
      • 题解思路
      • 代码
      • 改进点
    • 15. 三数之和
      • 题解思路
      • 代码
    • 2. 两数相加
      • 题解思路
      • 代码
    • 5. 最长回文子串
      • 题解思路
      • 代码
      • 代码改进思路
    • 108. 将有序数组转换为二叉搜索树
      • 题解思路
      • 代码
    • 33. 搜索旋转排序数组
      • 题解思路
      • 代码
    • 231. 2的幂
      • 题解思路
      • 代码
      • 更好的做法
    • 88. 合并两个有序数组
      • 题解思路
      • 代码
      • 更好的办法:数组归并排序使用尾插
    • 9. 回文数
      • 题解思路
      • 代码
    • 344. 反转字符串
      • 题解思路
      • 代码
    • 206. 反转链表
      • 题解思路
      • 代码
    • 122. 买卖股票的最佳时机 II
      • 题解思路
      • 代码
    • 46. 全排列
      • 题解思路
      • 代码
    • 104. 二叉树的最大深度
      • 题解思路
      • 代码
    • 刷题中出现的坑

题目列表

剑指 Offer 09. 用两个栈实现队列

题解思路

  1. 使用两个栈,一个为输出栈,一个为输入栈
  2. 使用两个栈的原因在于栈的特性是先进后出,也就是逆序,而队列是先进先出,也就是顺序,两个栈就可以负负得正
  3. 为了保持队列先进先出的特性,必须在输出栈为空之后,才可以将输入栈的内容转移到输出栈

代码

class CQueue {
    //输入队列
    private Stack<Integer> stack1 = new Stack<Integer>();
    //输出队列
    private Stack<Integer> stack2 = new Stack<Integer>();

    public CQueue() {

    }

    public void appendTail(int value) {
        stack1.push(value);
    }

    public int deleteHead() {
        int result = -1;
        //判断输出栈是否为空,非空将输出栈的一个元素抛出
        if(!stack2.empty()){
            result = stack2.pop();
        }else{
            //此时输出栈为空,将输入栈内容输出到输出栈
            this.output();
            if(!stack2.empty()){
                result = stack2.pop();
            }
        }
        return result;
    }
    
    public void output(){
        while(!stack1.empty()){
            stack2.push(stack1.pop());
        }
    }
}

进阶学习

  1. 题目中使用了java.util包下内置的队列
  2. 查看源码可以发现内置的栈继承了vector这个类,故底层为一个vector数组,其方法也用了synchronized修饰,故是一个线性安全的栈

运行结果

面试题 02.03. 删除中间节点

题解思路

  1. 已经给出了要删除的结点,而且非单向链表,那么只能将后续的结点复制到要删除的结点那

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
    
            node.val = node.next.val;
            node.next = node.next.next;

    }
}

运行结果

在这里插入图片描述

思考

尝试了

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
    
            node = node.next;

    }
}

发现没法通过,图解如下(遍历结点时用的是next,但是next实则没有改变)leetCode心得_第1张图片

剑指 Offer 29. 顺时针打印矩阵

题解思路

  1. 循环依次顺时针地打印,使用k来计数,作为循环结束的条件
  2. 比较坑的点在于这个举证长度任意且输入的数据可能为空
  3. 我使用了try…catch捕捉ArrayIndexOutOfBoundsException异常才能执行通过

代码

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        int length = 0;
        int[] arr;
        try{
            length = matrix.length * matrix[0].length;
        }catch(ArrayIndexOutOfBoundsException e){
            return arr = new int[0];
        }

        arr = new int[length];
        int k = 0;
        for (int i = 0; k<length; ++i) {

            //上层,为了使得奇数时中心点可以输出,应该范围是i~atrix.length - 1 - i
            for (int j = i; (j <= matrix[0].length - 1 - i) && (k<length); ++j) {
                arr[k++]=matrix[i][j];
            }
            //右层
            for (int j = i+1; (j <= matrix.length - 2 - i) && (k<length); ++j) {
                arr[k++]=matrix[j][matrix[0].length - 1 - i];
            }
            //下层
            for (int j = matrix[0].length - 1 - i; (j >= i + 1) && (k<length); --j) {
                arr[k++]=matrix[matrix.length - 1 - i][j];
            }
            //左层
            for (int j = matrix.length - 1 - i; (j >= i + 1) && (k<length); --j) {
                arr[k++]=matrix[j][i];
            }
        }
        return arr;
    }

}

运行结果

在这里插入图片描述

54. 螺旋矩阵

题解思路

  1. 与上面的类似,仅把数组换成ArrayList

代码

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        int length = 0;
        List list = new ArrayList();
        try{
            length = matrix.length * matrix[0].length;
        }catch(ArrayIndexOutOfBoundsException e){
            return list;
        }

            int k = 0;
            for (int i = 0; k<length; ++i) {

                //上层,为了使得奇数时中心点可以输出,应该范围是i~atrix.length - 1 - i
                for (int j = i; (j <= matrix[0].length - 1 - i) && (k<length); ++j) {
                    list.add(matrix[i][j]);
                    ++k;
                }
                //右层
                for (int j = i+1; (j <= matrix.length - 2 - i) && (k<length); ++j) {
                    list.add(matrix[j][matrix[0].length - 1 - i]);
                    ++k;
                }
                //下层
                for (int j = matrix[0].length - 1 - i; (j >= i + 1) && (k<length); --j) {
                    list.add(matrix[matrix.length - 1 - i][j]);
                    ++k;
                }
                //左层
                for (int j = matrix.length - 1 - i; (j >= i + 1) && (k<length); --j) {
                    list.add(matrix[j][i]);
                    ++k;
                }
            }
            return list;


        }
}

运行结果

在这里插入图片描述

小结:

  1. LeetCode官方题解使用了方向数组更好理解
int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
  1. 方向数组也可以用于迷宫解法,普适性更强

较好的解法(转载)–通过缩短边界范围

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length == 0) return new int[0];
        int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
        int[] res = new int[(r + 1) * (b + 1)];
        while(true) {
            for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.
            if(++t > b) break;
            for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
            if(l > --r) break;
            for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
            if(t > --b) break;
            for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
            if(++l > r) break;
        }
        return res;
    }
}

作者:jyd
链接:https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/solution/mian-shi-ti-29-shun-shi-zhen-da-yin-ju-zhen-she-di/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

718. 最长重复子数组

题解思路

  1. 使用动态规划,dp代表以第i个元素和第j个元素结尾的连续子数组的最大长度
  2. 如果A[i]!=B[j],那么以第i个元素和第j个元素结尾的连续子数组的最大长度为0
  3. 故状态转移方程为dp[i][j] = dp[i-1][j-1] + 1(A[i]=B[j])或者dp[i][j] = 0(A[i]!=B[j])

代码

class Solution {
    public int findLength(int[] A, int[] B) {
        //dp代表以第i个元素和第j个元素结尾的连续子数组的最大长度
        int[][] dp = new int[A.length+1][B.length+1];
        int max = 0;
        for(int i = 0; i <= A.length; i++){
            for(int j = 0; j <= B.length; j++){
                if(i == 0 || j == 0){
                    dp[i][j] = 0;
                    continue;
                }else{
                    //dp中的i和j应该代表数组第几个,对应的数组下标应该是减1
                    if(A[i-1] == B[j-1]){
                        dp[i][j] = dp[i-1][j-1] + 1;
                    }else{
                        dp[i][j] = 0;
                    }
                    max = Math.max(max,dp[i][j]);
                }
            }
        }
        
        return max;
    }
}

运行结果

在这里插入图片描述

较好的解法

  1. 比较次数过多在于公共数组在两个数组的位置不同,使用滚动数组解决
  2. 一种有些看不懂的做法,二分查找 + 哈希

剑指 Offer 10- II. 青蛙跳台阶问题

题解思路

  1. 使用动态规划,dp代表n个台阶时对应跳台阶的方法
  2. 跳到第n台阶只能从第n-1或者n-2个台阶跳过去
  3. 故状态转移方程为dp[n]=dp[n-1]+dp[n-2]
  4. 初试状态/边界条件:dp[0]=1,dp[1]=1
  5. 此题的特殊性,结果需要都取模,根据题意取1000000007,如果不求一次dp都取模,结果会溢出导致出错

代码

class Solution {
    public int numWays(int n) {
        int num = 0;
        int[] dp = new int[101];
        dp[0] = 1;
        dp[1] = 1;

        for(int i=2; i<=n; ++i){
            dp[i] = (dp[i-1] + dp[i-2])%1000000007;
        }
        return dp[n];
    }
}

运行结果在这里插入图片描述

小结

  • 本质就是斐波那契数列的变形

面试题 08.01. 三步问题

题解思路

  1. 与青蛙跳台阶一样解法
  2. 状态转移方程 dp[i] = (dp[i-1] + dp[i-2] + dp[i-3])
  3. 不同点在于使用了long,防止dp[i-1] + dp[i-2] + dp[i-3]相加时会溢出,导致出错

代码

class Solution {
    public int waysToStep(int n) {
        long[] dp = new long[n+1];
        dp[0] = 1;
        if(n>=1){dp[1] = 1;}
        if(n>=2){dp[2] = 2;}

        for(int i=3; i<=n; ++i){
            dp[i] = (dp[i-1] + dp[i-2] + dp[i-3])%1000000007;
        }
        return (int)dp[n];
    }
}

14. 最长公共前缀

题解思路

  1. 对传入的字符串数组时空的处理
  2. 找到最小的公共长度
  3. 从第一个字符串找第i个字符,与字符数组其他的第i个字符比较,如果全部相等就可以添加到公共前缀上,只要有一个不相等就退出整个循环
  4. 有字符串拼接操作,使用了StringBuffer,转成String时使用了toString方法

代码

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0){
            return "";
        }
        StringBuffer comPre = new StringBuffer("");
        char temp;
        //找到最小的字符串长度
        int length = 100000;
        for (int i = 0; i < strs.length; ++i){
            length = (length > strs[i].length()) ? strs[i].length() : length;
        }
        //从第一个字符串找第i个字符,与字符数组其他的第i个字符比较
        for(int i=0; i<length; ++i){
            int tag = 1;
            temp = strs[0].charAt(i);
            //第j个字符串的第i个字符与第一个字符串中找到第i个字符不相等,循环结束
            for(int j =1; j<strs.length;++j){
                if(temp != strs[j].charAt(i)){
                    tag = 0;
                    break;
                }
            }
            if(tag==1){
                comPre.append(temp);
            }else{
                break;
            }
        }
        //StringBuffer的toString方法可以转成String
        return  comPre.toString();
    }
}

61. 旋转链表

题解思路

  1. 对传入的链表为空以及k为0的处理
  2. 求链表长度,与此同时找到最后结点,k对聊表长度取模
  3. 处理改变后的k为0的情况
  4. 旋转链表的本质是找到lenth-k的结点,将他变为最后结点,将lenth-k的下一链表结点作为首结点

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
    //对传入的链表为空以及k为0的处理
        if(head == null || k == 0){
            return head;
        }
        //求链表长度
        int length = 0;
        ListNode pNode = head;
        //最后结点
        ListNode pEnd = null;
        //旋转后的新首结点
        ListNode pNewHead = null;
        while(pNode!=null){
            length++;
            if(pNode.next==null){
                //说明是最后一个结点
                pEnd = pNode;
                break;
            }
            pNode = pNode.next;
        }
        //如果k大于链表的长度则取模
        if(k>=length){
            k = k%length;
        }
        if(k == 0){
            return head;
        }
        //找到第length-k个结点,将他的下一个结点赋值给pNewHead,然后将其next变为null
        pNode = head;
        int count = 1;
        while(true){
            if(count == (length - k)){
                break;
            }
            ++count;
            pNode = pNode.next;
        }
        //此时的pNode为第length-k个结点
        pNewHead = pNode.next;
        pNode.next = null;
        pEnd.next = head;
        return pNewHead;
    }
}

运行结果

在这里插入图片描述

8. 字符串转换整数 (atoi)

题解思路

  1. 去空格,使用String中的trim方法
  2. 获取去空格后的第一个字符,如果为正负符号则保存到一个symbol的字符变量中
  3. 判断每个字符是否为数字,是的话加入到数字字符串
  4. 将数字字符串前缀为0的去掉
  5. 通过长度判断是否溢出,数字字符串长度大于10肯定溢出,小于10肯定不溢出,等于10时使用String中的compareTo方法
  6. 细节点:对于传入空字符串和null的处理,去掉空格后的空字符串的处理,前缀为0的处理

代码

class Solution {
    public int myAtoi(String str) {
        //对空字符串处理,返回0代表无法处理
        if(str == null ){
            return 0;
        }
        //temp用于存储数字
        StringBuffer temp = new StringBuffer("");
        //symbol用于存储符号,默认为正
        char symbol = '+';
        //将字符串首尾的空格去除,并验证此时的str是否为""
        str = str.trim();
        if("".equals(str)){
            return 0;
        }
        //对字符串第一个字符的处理
        char First = str.charAt(0);
        if(First=='+' || First=='-' ){
            symbol = First;
        }else if(First>=48 && First<=57){
            temp.append(First);
        }else{
            //字符此时非正负号和数字
            return 0;
        }
        char t;
        for(int i=1;i<str.length();++i){
            t = str.charAt(i);
            if(t>=48 && t<=57){
                temp.append(t);
            }else {
                //此时非数字,结束循环
                break;
            }
        }
        str = temp.toString();
        //验证此时的str是否为"",输入为"+"时会出现这种情况
        if("".equals(str)){
            return 0;
        }
        //将字符前缀为0的部分去除,即找到第一个不为0的字符
        int i = 0;
        while(i<str.length()&&str.charAt(i)==48){
            ++i;
        }
        if(i<str.length()){
            str = str.substring(i);
        }else{
            //证明str全为0
            return 0;
        }

        int result = 0;
        //Integer会不会溢出问题
         if(str.length()>10){
             //溢出
             if(symbol == '-'){
                 result = -2147483648;
             }else{
                 result = 2147483647;
             }
         }else if(str.length()<10){
             //肯定没溢出
             if(symbol == '-'){
                 result = Integer.valueOf("-"+str);
             }else{
                 result = Integer.valueOf(str);
             }
         }else {
             //数字位数为10,使用字典序的比较方式
             if(symbol == '-'){
                 if(str.compareTo("2147483648")>0){
                     //溢出
                     result = -2147483648;
                 }else {
                     result = Integer.valueOf("-"+str);
                 }
             }else{
                 if(str.compareTo("2147483647")>0){
                     //溢出
                     result = 2147483647;
                 }else {
                     result = Integer.valueOf(str);
                 }
             }
         }
         return  result;
    }
}

比较容易理解的做法

public class Solution {
    public int myAtoi(String str) {
        char[] chars = str.toCharArray();
        int n = chars.length;
        int idx = 0;
        while (idx < n && chars[idx] == ' ') {
            // 去掉前导空格
            idx++;
        }
        if (idx == n) {
            //去掉前导空格以后到了末尾了
            return 0;
        }
        boolean negative = false;
        if (chars[idx] == '-') {
            //遇到负号
            negative = true;
            idx++;
        } else if (chars[idx] == '+') {
            // 遇到正号
            idx++;
        } else if (!Character.isDigit(chars[idx])) {
            // 其他符号
            return 0;
        }
        int ans = 0;
        while (idx < n && Character.isDigit(chars[idx])) {
            int digit = chars[idx] - '0';
            if (ans > (Integer.MAX_VALUE - digit) / 10) {
                // 本来应该是 ans * 10 + digit > Integer.MAX_VALUE
                // 但是 *10 和 + digit 都有可能越界,所有都移动到右边去就可以了。
                return negative? Integer.MIN_VALUE : Integer.MAX_VALUE;
            }
            ans = ans * 10 + digit;
            idx++;
        }
        return negative? -ans : ans;
    }
}

不明觉历的做法–有限状态机

leetCode心得_第2张图片

class Solution {
    
    class Automaton {
    final String START = "start";
    final String SIGNED = "signed";
    final String IN_NUM = "in_number";
    final String END = "end";
    String state = START;
    Map<String, String[]> map;
    public int sign = 1;
    public long ans = 0;

    public Automaton() {
        map = new HashMap<>();
        map.put(START, new String[]{START, SIGNED, IN_NUM, END});
        map.put(SIGNED, new String[]{END, END, IN_NUM, END});
        map.put(IN_NUM, new String[]{END, END, IN_NUM, END});
        map.put(END, new String[]{END, END, END, END});
    }

    public int get_col(char c) {
        if (c == ' ') return 0;
        if (c == '+' || c == '-') return 1;
        if (c >= '0' && c <= '9') return 2;
        return 3;
    }

    public void get(char c) {
        state = map.get(state)[get_col(c)];
        if (state.equals(IN_NUM)) {
            ans = ans * 10 + c - '0';
            if (sign == 1) {
                ans = Math.min(ans, Integer.MAX_VALUE);
            } else {
                // -(long)Integer.MIN_VALUE,这个操作有点东西,不然越界
                ans = Math.min(ans, -(long)Integer.MIN_VALUE);
            }
        } else if (state.equals(SIGNED))
            sign = c == '+' ? 1 : -1;
    }
    }
    
    public int myAtoi(String str) {
        Automaton automaton = new Automaton();
        char[] c = str.toCharArray();
        for (char ch : c) {
            automaton.get(ch);
        }
        return automaton.sign * ((int) automaton.ans);
    }
}

378. 有序矩阵中第K小的元素

题解思路

  1. 第k小元素,而矩阵已经基本有序
  2. 将矩阵转为一维数组,并使用插入排序进行排序(基本有序使用插入排序会比较快)

代码

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int[] arr = insertSort(matrix);
        return arr[k-1];
    }
    //定义一个插入排序算法,将矩阵转为n*n长度的一维数组
    public int[] insertSort(int[][] matrix){
        int length = matrix.length;
        int temp;
        int[] arr = new int[length*length];
        for(int i=0; i<length; ++i){
            for(int j=0; j<length; ++j){
                temp = matrix[i][j];
                //插入的是第i*length+j个
                //找到按序插入的位置t
                int t = i*length+j;
                while(t>0 && arr[t-1]>temp){
                    //右移
                    arr[t] = arr[t-1];
                    t--;
                }
                arr[t] = temp;
            }
        }
        return arr;
    }
}

另一种思路

  1. 矩阵左上角元素必定最小
  2. 每取出一个元素将右边和下边元素放入,找到现有最小的元素取出
  3. 不断循环1和2直到取出第k个元素

官网使用二分查找leetCode心得_第3张图片

7. 整数反转

题解思路

  1. 负数转为正数计算出结果后在乘(-1)
  2. 注意x==-2147483648的判断,在负数转为正数的时候会溢出
  3. 使用了(2147483648L-x1%10)/10>=result判断翻转后是否溢出

代码

class Solution {
    public int reverse(int x) {
        int result = 0;
        if(x>=0){
            while(x!=0){
                if((2147483647-x%10)/10>=result){
                    result = x%10+result*10;
                    x/=10;
                }else {
                    //溢出
                    result = 0;
                    break;
                }
            }
        }else{
            if(x==-2147483648){
                return 0;
            }
            int x1 = -x;
            while(x1!=0){
                if((2147483648L-x1%10)/10>=result){
                    result = x1%10+result*10;
                    x1/=10;
                }else {
                    //溢出
                    result = 0;
                    break;
                }
            }
            result = (-1)*result;
        }
        return result;
    }

}

改进点

  • 不论是正数还是负数都可以用int pop = x % 10; x /= 10;rev = rev * 10 + pop;

15. 三数之和

题解思路

  1. 对数组排序
  2. 枚举时保证第一个元素和第二元素不重复
  3. 枚举第二层的循环与第三次循环可以同时扫描,最终

代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //排序
        Arrays.sort(nums);
        //使用暴力解法
        List<List<Integer>> list1 = new ArrayList<>() ;
        List<Integer> list;
        int n = nums.length;
        int target;
        for(int i = 0; (i<nums.length-2);++i){
            //从第二个开始时要找到与之前枚举过的不同的第一个元素
            if(i>0&& nums[i-1]==nums[i]){
                continue;
            }
            target = -nums[i];
            //k代表第三个元素位置
            int k = n-1;
            for (int j = i+1;(j<nums.length-1);++j){
                //找到没有枚举过的第二元素
                if(j>i+1 && nums[j-1]==nums[j]){
                    continue;
                }
                while(j<k && nums[j]+nums[k]>target){
                    --k;
                }
                if(j==k){
                    break;
                }
                if(nums[j]+nums[k]==target){
                    list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[j]);
                    list.add(nums[k]);
                    list1.add(list);


                }
                //执行到这为找不到与第二个元素匹配的第三元素,此时枚举的第二元素排除符合条件可能

            }
        }
        return list1;

    }
}

2. 两数相加

题解思路

  1. 类似两个数组合并,从链表第一位开始,进位与两链表对应的值相加得到进位,剩余位
  2. 将值保留在链表1中,并保留一个指向当前计算出的链表的前一结点,方便后续链接
  3. 若指向链表1或者2的结点已经为空,证明有一个位数较多,接下来就是这链表与进位的加法

代码

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //对传入的链为空的处理

        //设置保留进位和剩余位,和
        int carry = 0,remain,sum;
        //设置两个链头指向两链表
        ListNode pNode1 = l1;
        ListNode pNode2 = l2;
        //设置两个链指向两链表的前一个结点
        ListNode preNode1 = null;
        ListNode preNode2 = null;
        while (pNode1 != null && pNode2 != null){
            sum = pNode1.val + pNode2.val+carry;
            remain = sum%10;
            carry = sum/10;
            //保留l1作为新链
            pNode1.val = remain;
            //更新链的指向
            preNode1 = pNode1;
            pNode2 = pNode2.next;
            pNode1 = pNode1.next;
        }
        if(pNode1 == null && pNode2 != null){
            preNode1.next = pNode2;
            pNode1 = pNode2;
            //更新pNode1的所有结点在接收进位后的变化
            while(carry>0 && pNode1 != null){
                sum = pNode1.val + carry;
                pNode1.val = sum%10;
                carry = sum/10;
                preNode1= pNode1;
                pNode1 = pNode1.next;
            }
            if(pNode1 == null && carry>0){
                preNode1.next = new ListNode(carry);
            }
        }
        if(pNode2 == null && pNode1 != null){
            //更新pNode1的所有结点在接收进位后的变化

            while(carry>0 && pNode1 != null){
                sum = pNode1.val + carry;
                pNode1.val = sum%10;
                carry = sum/10;
                preNode1= pNode1;
                pNode1 = pNode1.next;
            }
            if(pNode1 == null && carry>0){
                preNode1.next = new ListNode(carry);
            }
        }
        if(pNode1 == null && pNode2 == null && carry>0){
            preNode1.next = new ListNode(carry);
        }
        return l1;
    }
}

改进点,循环条件改为pNode1 == null || pNode2 != null,null的设为0

5. 最长回文子串

题解思路

  1. 标记数组tag【长度,起始下标,结束下标】,用于记录找到的回文子串
  2. 遍历所有的元素,找到与首元素相同的下标,可能有机会为回文子串
  3. 如果找到可能为回文子串的长度小于已经找到的最大长度的回文子串,那么内层循环找不到比已经找到的最大长度的回文子串还大的子串,回到外层循环
  4. 判断是否为回文子串
  5. substring根据tag截取子串

代码

class Solution {
    public String longestPalindrome(String s) {
        if("".equals(s) || s==null){
            return s;
        }
        //标记数组tag【长度,起始下标,结束下标】,用于记录找到的回文子串
        int[] tag = {0,0,0};
        //遍历所有的元素
        First:for(int i = 0; i < s.length(); ++i){
            //找到与首元素相同的下标,可能有机会为回文子串
            for(int j=s.length()-1; (j > i) ;--j){
                if(s.charAt(i) != s.charAt(j)){
                    continue ;
                }
                if((j-i+1) <= tag[0]){
                    //如果找到可能为回文子串的长度小于已经找到的最大长度的回文子串
                    // 那么内层循环找不到比已经找到的最大长度的回文子串还大的子串,回到外层循环
                    continue First;
                }
                //判断是否为回文子串
                Boolean flag = true;//默认为回文子串
                int head = i+1;
                int tail = j-1;
                while((tail - head)>=1){
                    if(s.charAt(head) != s.charAt(tail)){
                        flag = false;
                        break ;
                    }
                    ++head;
                    --tail;
                }
                if(flag){
                    tag[0] = j-i+1;
                    tag[1] = i;
                    tag[2] = j;
                }
            }
        }
        //substring取不到结束下标的字符
        return s.substring(tag[1],tag[2]+1);
    }
}

代码改进思路

动态规划

108. 将有序数组转换为二叉搜索树

题解思路

  1. 使用递归方法
  2. 首先定义一个函数的作用是根据传入的一维升序数组,找到此一维数组对应的子树的根节点
  3. 函数的第一步进行边界判断,如果数组长度为0则返回null
  4. 那么根节点的左子树对应的一维数组下标范围就是0-mid-1,右子树对应的一维数组下标范围mid+1-length-1
  5. 那么递归调用函数,传入对应的子树一维数组的下标范围即可

代码

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        if(nums.length == 0){
            return null;
        }
        //寻找中间结点
        int mid = nums.length/2;
        TreeNode treeNode = new TreeNode(nums[mid]);
        //初始化左子树对应的一维数组
        int[] leftArr = new int[mid];
        for(int i = 0;i<mid;++i){
            leftArr[i] = nums[i];
        }
        //初始化右子树对应的一维数组
        int[] rightArr = new int[nums.length-mid-1];
        for(int i = 0;i<nums.length-mid-1;++i){
            rightArr[i] = nums[i+mid+1];
        }
        treeNode.left = sortedArrayToBST(leftArr);
        treeNode.right = sortedArrayToBST(rightArr);
        return treeNode;
    }
}

33. 搜索旋转排序数组

题解思路

  1. 使用二分查找
  2. 判断mid的位置–处于左边的上升数组与否
  3. 根据mid的位置就可以通过二分查找确定target在数组的位置
  4. 不断循环判断,循环结束条件为left<=right
  5. 判断结束后找到的可能值是否与target相等,相等返回对应值,不相等返回负一

代码

class Solution {
    public int search(int[] nums, int target) {
        //使用折半查找
        //nums = [4,5,6,7,0,1,2], target = 0,
        int left = 0;
        int right = nums.length-1;
        int mid = (left + right)/2;
        while(left < right){
            if(nums[mid]>=nums[left]){
                if(target<=nums[mid] && target >=nums[left]){
                    right = mid;
                    mid = (left + right)/2;
                }else{
                    left = mid + 1;
                    mid = (left + right)/2;
                }
            }else {
                if(target<=nums[mid] || target >=nums[left]){
                    right = mid;
                    mid = (left + right)/2;
                }else {
                    left = mid + 1;
                    mid = (left + right)/2;
                }
            }
        }
        if(left == right && nums[right] == target){
            return left;
        }else {
            return -1;
        }
    }
}

231. 2的幂

题解思路

  1. 对传入的整数小于等于0的判断
  2. 循环结束条件,n==1
  3. 默认为2的幂
  4. 循环判断,如果为偶数,此时n为原来一半,如果不是,则不为2的幂

代码

class Solution {
    public boolean isPowerOfTwo(int n) {
        //对于整数小于等于0的判断
        if(n <= 0){
            return false;
        }
        boolean flag = true;
        while(n != 1){
            if(n%2 == 0){
                n/=2;
            }else{
                flag = false;
                break;
            }
        }
        return flag;
    }
}

更好的做法

位运算 判断条件 (x & (-x)) == x

88. 合并两个有序数组

题解思路

  1. 使用插入排序
  2. 定义一个tail作为num1中第一个为空的下标
  3. 定义一个变量保存需要插入的位置
  4. 遍历数组二,找到每个元素对应的插入位置插入后,更新tail变量

代码

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //使用插入的方法
        //定义一个tail作为num1中第一个为空的下标
        int tail = m;
        //定义一个保持需要插入的位置
        int insertPos;
        int etem;
        for(int i = 0; i < n; ++i){
            etem = nums2[i];
            insertPos = tail;
            while(insertPos >= 1 && nums1[insertPos-1] > etem){
                nums1[insertPos] = nums1[insertPos-1];
                --insertPos;
            }
            nums1[insertPos] = etem;
            //插入完毕后tail后移动一位
            ++tail;
        }
    }
}

更好的办法:数组归并排序使用尾插

9. 回文数

题解思路

  1. 定义一个使得整数翻转的算法
  2. 对于传入的整数为负数时,返回false
  3. 返回翻转后的数==原本的数

代码

class Solution {
    public boolean isPalindrome(int x) {
        if(x < 0){
            return false;
        }
        return reverse(x) == x;
    }
    public int reverse(int x){
        int rev = 0;
        while(x != 0){
            rev = x % 10 + rev * 10;
            x /= 10;
        }
        return rev;
    }
}

344. 反转字符串

题解思路

  1. 使用head来保存需要交换得首下标
  2. 使用tail来保存需要交换得尾下标
  3. 循环条件为tail - head >= 1
  4. 循环结束后交换完毕

代码

class Solution {
    public void reverseString(char[] s) {
        int head = 0;
        int tail = s.length-1;
        char temp;
        while(tail - head >= 1){
            temp = s[head];
            s[head] = s[tail];
            s[tail] = temp;
            ++head;
            --tail;
        }
    }
}

206. 反转链表

题解思路

  1. 定义翻转链表的方法
  2. 边界判断head = null 或者head.next = null
  3. 先翻转原有链头第二个元素对应的链表,会返回一个新的链头,并改变原有链头第二个元素的指向,将原有链头的下一元素设为null
  4. 循环结束后交换完毕

代码

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null){
            //说明为最后一个链结点
            return head;
        }
        //使用递归的方法反转链表
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

122. 买卖股票的最佳时机 II

题解思路

  • 遍历到数组结束
    • 找到数组持续下降的地点—买入点
    • 找到数组持续上升的高点—卖出点
    • 每找到一对买入点和卖出点就为maxProfit增加售出利润

代码

class Solution {
    public int maxProfit(int[] prices) {
//         1.找到数组持续下降的地点---买入点
//         2.找到数组持续上升的高点---卖出点
//        重复1和2知道数组结束
        int lowpoint;
        int highPoint;
        int maxProfit = 0;
        for (int i = 0; i < prices.length; ++i) {
            while (i < prices.length - 1 && prices[i] >= prices[i + 1]) {
                /*找到数组持续下降的地点---买入点*/
                ++i;
            }
            if (i == prices.length) {
                break;
            } else {
                lowpoint = prices[i];
                ++i;
            }
            while (i < prices.length - 1 && prices[i] <= prices[i + 1]) {
                /*找到数组持续上升的高点---卖出点*/
                ++i;
            }
            if (i == prices.length) {
                break;
            } else {
                highPoint = prices[i];
            }
            maxProfit = maxProfit + highPoint - lowpoint;

        }
        return maxProfit;
    }
}

46. 全排列

题解思路

  • 将函数定义为找到一个数组的全排列
  • 选择数组的某个数作为list第k个元素,将其余的数作为新数组传入递归调用的函数
  • 使用了list的set方法,由于需要返回对于set位置的旧元素,底层是需要比较位置是否小于size,故需要先add元素,即为list初始化
  • 细节点:加入一个list时,其实加入的是他的地址,会导致得到的lists元素重复,{1,3,2}{1,3,2},故每次为lists添加元素时,新建一个temp,将list现有的元素放入

代码

class Solution {
    public List<Integer> list = new ArrayList<>(1);
    public List<List<Integer>> lists = new ArrayList<>();
    int f = 0;

    public List<List<Integer>> permute(int[] nums) {

        if (nums == null) {
            return null;
        }

        if (nums.length == 1) {
            //坑人的点,list由于是同个所以,会随着set变化而变化
            if (list.size() == 0) {
                list.add(0);
            }
            list.set(f, nums[0]);
            List<Integer> temp = new ArrayList<>();
            for (Integer i : list) {
                temp.add(i);
            }
            lists.add(temp);
            return lists;

        }
        for (int i = 0; i < nums.length; ++i) {
            if (f == 0) {
                list = new ArrayList<>(nums.length);
                for (int t = 0; t < nums.length; ++t) {
                    list.add(0);
                }
            }
            list.set(f++, nums[i]);
            int[] arr = new int[nums.length - 1];
            int k = 0;
            for (int j = 0; j < nums.length; ++j) {
                if (i != j) {
                    arr[k++] = nums[j];
                }
            }
            permute(arr);
            --f;
        }
        return lists;
    }
}

104. 二叉树的最大深度

题解思路

  • 定义递归方法的含义为求传入一个根节点,返回以其为根的子树的最大深度
  • 边界判断
    • 传入的为null;说明深度为0
    • 传入的结点左子树和右子树都为0,说明为叶子结点,深度为1
  • 该子树的深度为左子树的深度和右子树的深度的最大值+1

代码

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        } else {
            return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
        }

    }
}

刷题中出现的坑

  • 对于传入的数组为空值的处理
  • 注意题目给出的参数的范围
  • 最长子序列是非连续的还是连续的
  • 旋转链表的题目中对k==lenth长度的处理,防止出现空值异常
  • 将字符串解析为数字时对前缀为0的处理
  • java中int的表示范围,要注意溢出,像在跳台阶问题和字符串转换整数 (atoi)都涉及了这个问题
  • 字符串方法substring取不到结束下标参数对应的字符
  • ArrayList的set方法,需要位置小于size,因为需要返回旧元素
  • 递归有时会用到的一个技巧,设置一个k代表递归的层数

你可能感兴趣的:(leetCode心得)