leetcode题库刷题(不按顺序)

目录

 leetcode(Java版本)

10.正则表达式的匹配(10表示在leetcode中是第10题以下不重复说明)

44.通配符匹配

3.无重复字符的最长子串

2.两个数相加

43.字符串相乘

1.两数相加

15.三数之和

16.最接近的三数之和

18.四数之和

5.最长回文子串

23.合并K个有序链表(分治思想)

32.最长有效括号(动态规划、类似于回文字符串)

204.计算质数

4.寻找两个有序数组的中位数

34.在排序数组中查找元素的开始和结束位置

153.寻找旋转排序数组中的最小值

81.搜索旋转排序数组中的最小值2

64.最小路径和(经典动态规划问题)

94.不同的二叉搜索树

95.不同的二叉搜索树2

94.二叉树的中序遍历


 leetcode(Java版)

10.正则表达式的匹配(10表示在leetcode中是第10题以下不重复说明)

只需要了解动态规划即可。

解法:动态规划,自顶向下的解题思路。关键是得出转移方程,即已知dp[i][j](表示s的前i个与p的前j个能否匹配):

1.当s[i]=p[j]的时候,dp[i][j] = dp[i-1][j-1];

2.当p[j]='.'的时候,同上;

3.当p[j]='*'的时候,'*'表示前面的字符可以出现0次及其以上,可以分为以下几种情况:

  3.1 当p[j-1]不等于s[i]时,dp[i][j] = dp[i][j-2]

  3.2当p[j-1]等于'.'时或者p[j-1]等于s[i],是

dp[i][j] = dp[i-1][j] // 多个字符匹配的情况 (假设p[j-1]重复两次以上,因此回溯i游标)   
or dp[i][j] = dp[i][j-1] // 单个字符匹配的情况(假设p[j-1]重复一次)
or dp[i][j] = dp[i][j-2] // 没有匹配的情况(假设p[j-1]重复零次)

class Solution {
   public boolean isMatch(String s,String p){
            if (s == null || p == null) {
                return false;
            }
            boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
            dp[0][0] = true;//dp[i][j] 表示 s 的前 i 个是否能被 p 的前 j 个匹配
            for (int i = 0; i < p.length(); i++) { // here's the p's length, not s's
                if (p.charAt(i) == '*' && dp[0][i - 1]) {
                    dp[0][i + 1] = true; // here's y axis should be i+1
                }
            }
            for (int i = 0; i < s.length(); i++) {
                for (int j = 0; j < p.length(); j++) {
                    if (p.charAt(j) == '.' || p.charAt(j) == s.charAt(i)) {//如果是任意元素 或者是对于元素匹配
                        dp[i + 1][j + 1] = dp[i][j];
                    }
                    if (p.charAt(j) == '*') {
                        if (p.charAt(j - 1) != s.charAt(i) && p.charAt(j - 1) != '.') {//如果前一个元素不匹配 且不为任意元素
                            dp[i + 1][j + 1] = dp[i + 1][j - 1];
                        } else {
                            dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i][j + 1] || dp[i + 1][j - 1]);
                            /*
                            dp[i][j] = dp[i-1][j] // 多个字符匹配的情况	
                            or dp[i][j] = dp[i][j-1] // 单个字符匹配的情况
                            or dp[i][j] = dp[i][j-2] // 没有匹配的情况
                             */
                            
                        }
                    }
                }
            }
            return dp[s.length()][p.length()];
        }
}

 

3.无重复字符的最长子串

解法1:优化的滑动窗口(java的HashMap实现)

class Solution{
    //改良滑动窗口法
    public int lengthOfLongestSubstring(String s){
        if(s == null){
            return 0;
        }
        //定义字符到索引的映射,避免简单滑动窗口(left<=x<i)带来的弊端:极端情况下可能所有的字符会被访问两次
        HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        //max表示s串当前循环最大的无重复子串的长度,left表示指向“滑动窗口”的最左端
        int max = 0;
        int left = 0;
        for(int i = 0;i < s.length();i ++){
            if(map.containsKey(s.charAt(i))){
                //这一步比较关键,[left,i)之间若s[i]在上述区间之间重复,只需让left变成max(left,map.get(s.charAt(i)) + 1)
                //选用第16行而不是第17行的原因是left游标不能回溯,简单滑动窗口由于区间上下游标增1移动故不可能回溯
                //因此增加Math.max(left,map.get(s.charAt(i)) + 1);保证了left游标不可能回溯
                left = Math.max(left,map.get(s.charAt(i)) + 1);
                //left = map.get(s.charAt(i)) + 1;
            }
            //这一步其实不管是否map的关键字中是否有s.charAt(i),s.charAt(i)和i都得被添加到map中
            //因此没有else
            map.put(s.charAt(i),i);
            //每一次循环更新max的值
            max = Math.max(max,i-left+1);
        }
        return max;
    }
}

   解法2:用int []来实现HashMap的功能

class Solution{
    //改良滑动窗口法
    public int lengthOfLongestSubstring(String s){
        if(s == null){
            return 0;
        }
        //定义字符到索引的映射,避免简单滑动窗口(left<=x<i)带来的弊端:极端情况下可能所有的字符会被访问两次
        //HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        int[] index = new int[128];
        
        //max表示s串当前循环最大的无重复子串的长度,left表示指向“滑动窗口”的最左端
        int max = 0;
        int left = 0;
        
        for(int i = 0;i < s.length();i ++){
            /*
            if(map.containsKey(s.charAt(i))){
                //这一步比较关键,[left,i)之间若s[i]在上述区间之间重复,只需让left变成max(left,map.get(s.charAt(i)) + 1)
                //选用第16行而不是第17行的原因是left游标不能回溯,简单滑动窗口由于区间上下游标增1移动故不可能回溯
                //因此增加Math.max(left,map.get(s.charAt(i)) + 1);保证了left游标不可能回溯
                left = Math.max(left,map.get(s.charAt(i)) + 1);
                //left = map.get(s.charAt(i)) + 1;
            }
            */
            left = Math.max(left,index[s.charAt(i)]);
            index[s.charAt(i)] = i+1;
            
            //这一步其实不管是否map的关键字中是否有s.charAt(i),s.charAt(i)和i都得被添加到map中
            //因此没有else
            //map.put(s.charAt(i),i);
            //存入i+1避免24行代码无法判断是否已经存储
            //index[s.charAt(i)] = i+1;
            //每一次循环更新max的值
            max = Math.max(max,i-left+1);
        }
        return max;
    }
}

2.两个数相加

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }//单链表节点的构造方法没有next的初始化
 * }
 */
 //默认链表存在头指针
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //初始化目标节点的时候没有赋予next的值
        ListNode result_node = new ListNode(0);
        ListNode p = l1,q = l2,cuur = result_node;
        int carry = 0;
        //下面的条件由于是 || 的关系因此两个链表会一直被遍历,短的链表相应的位置补零
        while(q != null || p != null){
            //以下两行代码保证了长度不够的链表,在相应的位置上补零,以便按照最长的链表继续遍历下去
            int x = (p == null) ? 0 : p.val;
            int y = (q == null) ? 0 : q.val;
            //下面一行代码充分体现了result_node是指向目标链表的头结点的
            cuur.next = new ListNode((x + y + carry) % 10);
            carry = (x + y + carry) / 10;
            if(p != null)
                p = p.next;
            if(q != null)
                q = q.next;
            cuur = cuur.next;
        }
        if(carry != 0){
            cuur.next = new ListNode(carry);
            cuur.next.next = null;
        }
        return result_node.next;
    }
}

那么,经过这一道题,可以得到遍历单链表的一个诀窍(就是想象这两个单链表的长度相等,那么较短的单链表的伪节点值域就是0)

43.字符串相乘

解法:需要注意的是java语言获得数组、字符串、集合长度的方法不同:

数组:array.length;(此处应该注意,length与length()的区别)

字符串:string.length();

集合:list.size();

class Solution {
    public String multiply(String num1, String num2) {
        //String.equals()而不是"=="
        if(num1.equals("0") || num2.equals("0")){
            return "0";
        }
        //两个数相乘,最大的位数是这两个相乘的位数之和
        int[] result = new int[num1.length() + num2.length()];
        //String类型的对象的值是不可改变的,每次对String的操作都会生成新的String对象。
        //因此使用StringBuilder类型的对象
        StringBuilder str = new StringBuilder();
        for(int i = num1.length()-1;i >= 0;i --){
            int n1 = num1.charAt(i) - '0';
            for(int j = num2.length() - 1;j >= 0;j --){
                //'0'的阿西克码值是48
                int n2 = num2.charAt(j) - 48;
                //n1*n2的结果,可以看成一个两位数(0x or xy),那么十位被存在result[i + j],个位被存储在result[i + j + 1]
                //num1[i] * num2[j]的结果是二者相乘的结果加上上一次相乘(很多种情况)的进位result[i+j+1]
                int sum = result[i+j+1] + n1 * n2;
                //更新result[i + j + 1]
                result[i + j + 1] = sum % 10;
                //可以理解为num1[i] * num2[j]的进位进到result[i + j],因此是"+="
                result[i + j]  += sum / 10;
            }
        }
        for(int k = 0;k < result.length;k ++){
            // 结果最大的位数是两数相加,最小的位数是两数相加减去1
            if(k == 0 && result[k] == 0)
                continue;
            str.append(result[k]);
        }
        return str.toString();
    }
}

1.两数相加

解法1(两遍哈希表:因为需要用到两次迭代):定义一个HashMap,有效避免了二重循环的问题。

判断一维数组(A)是否为空:A.length == 0 or A == null

判断二维数组(A)是否为空:第一行的数组长度是否为空,即A[0].length == 0,考虑{},{{}},{0}

class Solution {
    public int[] twoSum(int[] nums, int target) {
        if(nums == null){
            return null;
        }
        //也可以换成下面一句
        //throw new IllegalArgumentException("所给数组不符合要求")
        
        //HashMap<Integer,Integer> map = new  HashMap<Integer,Integer>();
        HashMap<Integer,Integer> map = new  HashMap<>();
        int i = 0;
        for(i = 0;i < nums.length;i ++){
            map.put(nums[i],i);
        }
        //HashMap的关键字集合中首先得含有target - nums[i],其次两者的下标不能相等
        for(i = 0;i < nums.length;i ++){
            if(map.containsKey(target - nums[i]) && map.get(target - nums[i]) != i){
                
                return new int[]{ i , map.get(target - nums[i])};
            }
        }
        return null;
    }
}

解法2:在构建哈希表的时候就判断是否满足条件。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

15.三数之和

解法:双指针问题,值得注意的是:List中,为null和isEmpty()是两个概念,为空意味着没有对对象分配内存空间,而后者表示这个集合没有元素,但是分配了内存空间,创建了空集合。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        
        List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(nums);
        int k = 0,i,j = nums.length;
        for(k = 0;k < nums.length;k ++){
            if(nums[k] > 0)
                break;
            if(k > 0 && nums[k] == nums[k-1])
                continue;
            i = k + 1;
            j = nums.length - 1;
            while(i<j){
                int sum = nums[k] + nums[i] + nums[j];
                if(sum == 0){
                    ans.add(new ArrayList<Integer>(Arrays.asList(nums[k],nums[i],nums[j])));
                    while(i < j && nums[i] == nums[++ i]);
                    while(i < j && nums[j] == nums[-- j]);
                }else if(sum < 0){
                    while(i < j && nums[i] == nums[++ i]);
                }else{
                    while(i < j && nums[j] == nums[-- j]);
                }
            }
        }
        return ans;
    }
}

16.最接近的三数之和

解法:简单的双指针的解法,当差值为零时,直接返回。时间复杂度和空间复杂度都是中等。排序法加上双指针就是这类问题的统一解法。

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int ans = nums[0] + nums[1] + nums[2];
        int value = Math.abs(target - ans);
        int k;
        int i,j;
        for(k = 0;k < nums.length - 1;k ++){
            i = k + 1;
            j = nums.length - 1;
            /*
            if(k > 0 && nums[k] == nums[k - 1])
                continue;
                */
            while(i < j){
                if(value == 0){
                    return ans;
                }
                if(value > Math.abs(nums[k] + nums[i] + nums[j] - target)){
                    ans = nums[k] + nums[i] + nums[j];
                    value = Math.abs(nums[k] + nums[i] + nums[j] - target);
                    
                }
                if(nums[k] + nums[i] + nums[j] > target){
                    while(i < j && nums[j] == nums[-- j]);
                }
                else if(nums[k] + nums[i] + nums[j] < target){
                    while(i < j && nums[i] == nums[++ i]);
                }
                
            }
        }
        return ans;
    }
}

18.四数之和

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        if(nums.length < 4 || nums == null){
            return result;
        }

        int k,p,i,j;
        int length = nums.length;
        for(p = 0;p < nums.length - 3;p ++){
            if(p > 0 && nums[p] == nums[p-1])
                continue;
            int min1 = nums[p] + nums[p + 1] +nums[p + 2] + nums[p + 3];
            int max1 = nums[p] + nums[length - 1] +nums[length - 2] + nums[length - 3];
            if(min1 > target || max1 < target)
                continue;
            
            for(k = p + 1;k < nums.length - 2;k ++){
                if(k > p+1 && nums[k] == nums[k - 1])
                    continue;
                int min2 = nums[p] + nums[k] +nums[k + 1] + nums[k + 2];
                int max2 = nums[k] + nums[p] +nums[length - 1] + nums[length - 2];
                if(min2 > target || max2 < target)
                    continue;
                i = k + 1;
                j = nums.length - 1;
                
                while(i < j){
                    int sum = nums[p] + nums[k] + nums[i] +nums[j];
                    if(sum == target){
                        ArrayList<Integer> tmp = new ArrayList<Integer>(Arrays.asList(nums[p],
                                        nums[k],
                                        nums[i],
                                        nums[j]));
                        result.add(tmp);
                        while(i < j && nums[i] == nums[++ i]);
                        while(i < j && nums[j] == nums[-- j]);
                    }
                     
                    else if(sum < target){
                        while(i < j && nums[i] == nums[++ i]);
                    }else{
                        while(i < j && nums[j] == nums[-- j]);
                    }
                }
            }
        }
      return result;
    }
    
}

5.最长回文子串

解法1:回溯法:s[l][r]表示s中从s[l]到s[r]是回文字符串那么可以得到以下状态转移方程:

if(s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l + 1][r - 1])){

                    dp[l][r] = true;

}

这种方法的时间复杂度和空间复杂度都不是很好:

执行用时 :48 ms, 在所有 java 提交中击败了52.01% 的用户

内存消耗 :39.3 MB, 在所有 java 提交中击败了60.36%的用户

class Solution {
    public String longestPalindrome(String s) {
        int length = s.length();
        if(length <= 1){
            return s;
        }
        int longgest = 1;
        //String ans = null;
        //substring()方法而不是S大写
        String ans = s.substring(0, 1);
        boolean[][] dp = new boolean[length][length];
        //r指向待判断是否是回文子串的右边,而ll指向左边,因此r从1开始遍历,l从0开始并且l < r
        for(int r = 1;r < length;r ++){
            for(int l = 0;l < r;l ++){
                //r - l <= 2表示dp[l+1][r-1]中要么有0个元素要么有1个元素,这两种情况都不用判断
                if(s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l + 1][r - 1])){
                    dp[l][r] = true;
                    if(r - l +1 > longgest){
                    longgest = r - l + 1;
                    ans = s.substring(l,r + 1);
                    }   
                }
                
            }
        }
        return ans;
    }
}

 解法2:Manacher's Algorithm 马拉车算法:时间复杂度和空间复杂度都可以接受

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        if(len < 2){
            return s;
        }
        String new_str = addBoundary(s, '#');
        int new_len = 2 * len + 1;
        int max_len = 1;
        int start = 0;
        for(int i = 0;i < new_len;i ++){
            int cur_len = centerSpread(new_str,i);
            if(cur_len > max_len){
                max_len = cur_len;
                start = (i - max_len) / 2;
            }
        }
        
        return s.substring(start,start + max_len);
    }
    public String addBoundary(String s,char div){
        int len = s.length();
        if(len == 0){
            return "";
        }
        if(s.indexOf(div) != -1){
            throw new IllegalArgumentException("参数错误");
        }
        StringBuilder str = new StringBuilder();
        for(int i = 0;i < len;i ++){
            str.append(div);
            str.append(s.charAt(i));
        }
        str.append(div);
        return str.toString();
    }
    private int centerSpread(String s,int center){
        int len = s.length();
        int i = center - 1;
        int j = center + 1;
        int step = 0;
        while(i >= 0 && j < len && s.charAt(i) == s.charAt(j)){
            -- i;
            ++ j;
            ++ step;
        }
        return step;
    }
}

23.合并K个有序链表(分治思想)

解法1:分治思想

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0){
            return null;
        }else{
            return solve(lists,0,lists.length - 1);
        }

    }
    private ListNode solve(ListNode[] lists,int first,int second){
        if(first == second){
            return lists[first];
        }
        //以下体现分治思想
        int mid = (first + second) >> 1;
        ListNode node1 = solve(lists,first,mid);
        ListNode node2 = solve(lists,mid + 1,second);
        return merge2Listnodes(node1,node2);
    }
    private ListNode merge2Listnodes(ListNode first,ListNode second){
        //这是归并两个ListNode的算法
        //这两个链表有一个为空,那么就返回对方
        if(first == null || second == null){
            return first == null ? second : first;
        }
        if(first.val < second.val){
            first.next = merge2Listnodes(first.next,second);
            return first;
        }else{
            second.next = merge2Listnodes(first,second.next);
            return second;
        }
    }
}

32.最长有效括号(动态规划、类似于回文字符串)

解法1:动态规划,不建议使用动态规划,太过与复杂。

class Solution {
    
    public int longestValidParentheses(String s) {
       
        int maxlen = 0;
        //dp[i]代表以s[i]结尾的字符串的满足要求的长度
        int[] dp = new int[s.length()];
        //dp[0] = 0;
        for(int i = 1;i < s.length();i ++){
            if(s.charAt(i) == ')'){
                if(s.charAt(i - 1) == '('){
                    /*
                    if(i >= 2){
                        dp[i] = dp[i - 2] + 2;
                    }else{
                        dp[i] = 2;
                    }
                    */
                    dp[i] = ((i >= 2) ? dp[i - 2]:0) + 2;
                }
                //(i - dp[i - 1] >= 1)这个条件应该在前面,如果在后面会报错
                else if( (i - dp[i - 1] >= 1) && s.charAt(i - dp[i - 1] - 1) == '(' ){
                    dp[i] = dp[i - 1] + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                maxlen = Math.max(maxlen,dp[i]);
            }
        }
        return maxlen;
    }
}

解法2:计数器法

class Solution {
    
    public int longestValidParentheses(String s) {
        int left = 0,right = 0;
        int maxlen = 0;
        for(int i = 0;i < s.length();i ++){
            if(s.charAt(i) == '(')
                left ++;
            else{
                right ++;
            }
            if(left == right)
                maxlen = Math.max(maxlen,2 * right);
            else if(right > left){
                left = 0;
                right = 0;
            }
            

        }
        left = 0;
        right = 0;
        for(int i = s.length() - 1;i >= 0;i --){
                if(s.charAt(i) == '(')
                    left ++;
                else
                    right ++;
                if(left == right)
                    maxlen = Math.max(maxlen,2 * right);
                else if(left > right){
                    left = 0;
                    right = 0;
                }
        }
    return maxlen;
    }
}

204.计算质数

解法1:质数打表法:比较适合n较大的情况,因此在这数较小的情况下时间复杂度和空间复杂度并不好

class Solution {
    public int countPrimes(int n) {
        return table(n);
    }
    private int table(int n){
        if(n < 3){
            return 0;
        }
        int[] prime = new int[n];
        boolean[] visit = new boolean[n];
        //为了表达简易,这里面false代表是质数
        visit[0] = visit[1] = true;
        int num = 0;
        for(int i = 2;i < n;i ++){
            if(visit[i] == false){
                num ++;
                prime[num] = i;
            }
            for(int j = 1;(j <= num && i * prime[j] < n);j ++){
                visit[i * prime[j]] = true;
                if(i % prime[j] == 0){
                    break;
                }
            }
        }
        int sum = 0;
        for(int k = 0;k < n;k ++){
            if(visit[k] == false){
                sum ++;
            }
        }
        return sum; 
    }
        
}

4.寻找两个有序数组的中位数

解法:由于题目要求的是时间复杂度是O(log(m + n)),因此很自然想到借用二分查找与递归结合进行解题

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        //无论m + n 是奇数还是偶数,中位数是第left个数和第right个数之和
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        //很尴尬,* 0.5 可以
        return (getKth(nums1,0,n - 1,nums2,0,m - 1,left) + getKth(nums1,0,n - 1,nums2,0,m - 1,right)) * 0.5;
    }
    //这个函数是为了在nums1和nums2中找出第K小的数
    private int getKth(int[] nums1,int start1,int end1,int[] nums2,int start2,int end2,int k){
        int length1 = end1 - start1 + 1;
        int length2 = end2 - start2 + 1;
        //让length1的长度小于length2,如果有一个数组空了,那么一定是length1
        if(length1 > length2)
            return getKth(nums2,start2,end2,nums1,start1,end1,k);
        /*
        try{
            if(length1 == 0) 
            return nums2[start2 + k - 1];
        }catch(Exception e){

        }
        */
        
        if(length1 == 0) 
            return nums2[start2 + k - 1];
        
        //递归结束条件
        if(k == 1){
            return Math.min(nums2[start2],nums1[start1]);
        }
        //此处体现出来二分查找的特点
        int i = start1 + Math.min(length1,k / 2) - 1;
        int j = start2 + Math.min(length2,k / 2) - 1;
        //小于等于和小于的结果一样
        if(nums1[i] < nums2[j]){
            return getKth(nums1,i + 1,end1,nums2,start2,end2,k - (i - start1 + 1));
        }else{
            return getKth(nums1,start1,end1,nums2,j + 1,end2,k - (j - start2 + 1));
        }
    }
}

34.在排序数组中查找元素的开始和结束位置

解法1:利用哈希表,但是速度很慢,题目要求了时间复杂度

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ans = new int[]{-1,-1};
        if(nums.length == 0){
            return ans;
        }
        HashMap<Integer,Integer> map_left = new HashMap<>();
        HashMap<Integer,Integer> map_right = new HashMap<>();
        int left = 0;
        int right = 0;
        //主要利用哈希表的覆盖原理,从左向右和从右向左扫描
        for(int i = 0;i < nums.length;i ++){
            map_left.put(nums[i],i);
        }
        for(int j = nums.length - 1;j > -1;j --){
            map_right.put(nums[j],j);
        }
        //可能左右相等
        if(map_left.containsKey(target)){
            left = map_right.get(target);
            right = map_left.get(target);
            ans[0] = left;
            ans[1] = right;
        }
        return ans;
    }

解法2:二分查找(因为题目要求的时间复杂度是O(logn)),分析二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。计算mid的时候为了防止溢出,可以采用 mid=left+(right-left)/2 

详见https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ans = {-1,-1};
        int left_index = search_bound(nums,target,true);
        if(left_index == nums.length || nums[left_index] != target)
            return ans;
        int right_index = search_bound(nums,target,false) - 1;
        ans[0] = left_index;
        ans[1] = right_index;
        return ans;
    }
    //flag表明是否是左边界查找
    private int search_bound(int[] nums,int target,boolean flag){
        int left = 0;
        int right = nums.length;
        while(left < right){
            int mid = (left + right) / 2;
            if(nums[mid] > target || (flag && target == nums[mid]))
                right = mid;
                /*
            else if(nums[mid] < target){
                left = mid + 1;
            }
            */
            else
                left = mid + 1;
              
        }
        return left;
    }
}

 上面这一种也可以,就是空间复杂度低一点,但是不能有两个else if那样leetcode的时间复杂度会不满足要求。 

153.寻找旋转排序数组中的最小值

解法1:时间复杂度和空间复杂度都不是很好,这个数组可能会发生旋转没有重复值

​class Solution {
    public int findMin(int[] nums) {
        Arrays.sort(nums);
        return nums[0];
    }
}​

解法2:二分查找:

class Solution {
    public int findMin(int[] nums) {
        if(nums.length == 0)
            throw new IllegalArgumentException("所给的数组有误");
        if(nums.length == 1)
            return nums[0];
        if(nums.length == 2){
            return Math.min(nums[0],nums[1]);
        }
        int left = 0;
        int right = nums.length - 1;
        while(left < right){
            //以nums[nums.length - 1]为哨兵
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[mid + 1] && mid + 1 < nums.length - 1)
                return nums[mid + 1];
            if( mid - 1 > 0 && nums[mid - 1] > nums[mid])
                return nums[mid];
            //绘制一个分段函数可以解释left和right的变化
            /*
            if(nums[mid] > nums[right]){
                left = mid + 1;
            }else if(nums[mid] < nums[right]){
                right = mid;
            }
            */
            //以nums[0]为哨兵,不可行,因为题目上是可能进行旋转,故不适合
            //当数组没有进行旋转的时候不通过
            if(nums[mid] < nums[0]){
                left = mid + 1;
            }else{
                right = mid;
            }
            
            
        }
        //返回left和right没有区别
        return nums[left];
    }
}

154.搜索旋转数组的最小值2

解法1:这个数组可能会旋转,另外这个数组存在重复元素,使用HashSet去重,时间复杂度和空间复杂度很差

class Solution {
    public int findMin(int[] nums) {
        if(nums == null){
            return -1;
        }
        HashSet<Integer> set = new HashSet<>();
        int min = nums[0];
        for(int i = 0;i < nums.length;i ++){
            set.add(nums[i]);
        }
        for(int value : set){
            if(value < min)
                min = value;
        }
        return min;
    }
}

解法2:因为数组基本有序因此使用二分查找来进行解题,这个解法也适用于153题的解法

class Solution {
    public int findMin(int[] nums) {
        //这个数组nums可以分为两个非递减的数组nums1和nums2
        //以下不能写成Exception,不然会报错
        if(nums.length == 0)
            throw new IllegalArgumentException("所给的数组为空!");
        if(nums.length == 1)
            return nums[0];
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            //nums[mid] > nums[right],证明最小值在nums2中,
            //并且一定不是nums[mid],因此令乐left = mid + 1
            if(nums[mid] > nums[right]){
                left = mid + 1;
            }else if(nums[mid] < nums[right]){
                //如果nums[mid] < nums[right],那么最小值一定在nums1中,但是有可能是nums[mid]
                //因此令right = mid
                right = mid;
            }else if(nums[mid] == nums[right]){
                //nums[mid]既可能在nums1中,又可能在nums2中
                //故令right = right - 1最稳妥
                right --;
            }
        }
        return nums[left];
    }
}

 

81.搜索旋转排序数组2

该数组一定会发生旋转,有重复值

解法1:先用Arrays.sort()进行排序,然后再用普通的二分查找进行寻找

class Solution {
    public boolean search(int[] nums, int target) {
        //首先这个题与153题的区别是首先数组一定会旋转,其次这个数组有重复元素
        //时间复杂度为O(Nlog(N))
        if(nums.length == 0)
            return false;
        if(nums.length == 1 && nums[0] == target)
            return true;
        Arrays.sort(nums);
        int left = 0;
        int right = nums.length - 1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] == target){
                return true;
            }else if(nums[mid] > target){
                right = mid - 1;
            }else
                left = mid + 1;
        }
        if(nums[left] == target)
            return true;
        return false;
    }
}

解法2:暴力递归,时间复杂度很好,但是空间复杂度很差

class Solution {
    public boolean search(int[] nums, int target) {
        if(nums.length == 0)
            return false;
        boolean ans = binary_search(nums,target,0,nums.length - 1);
        return ans;
    }
    private boolean binary_search(int[] nums,int target,int left,int right){
        if(left == right)
            return (nums[left] == target);
        if(left > right){
            return false;
        }
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] == target)
                return true;
            return binary_search(nums,target,mid + 1,right) || binary_search(nums,target,left,mid - 1);
               
        }
        return (nums[left] == target);
    }
}

 

解法3:因为数组基本有序,采用二分查找

class Solution {
    public boolean search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return true;
            if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
                left++;
                right--;
            } else if (nums[left] <= nums[mid]) {
                if (nums[left] <= target && target < nums[mid]) right = mid - 1;
                else left = mid + 1;
            } else {
                if (nums[mid] < target && target <= nums[right]) left = mid + 1;
                else right = mid - 1;
            }
        }
        return false;  
    }
}

 

64.最小路径和(经典动态规划问题)

解法:二维动态规划,时间复杂度和空间复杂度都很好,也容易想到

class Solution {
    public int minPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        //这个地方容易搞错,是m而不是m - 1
        int[][] dp = new int[m][n];
        //dp[i][j]表示到grid[i][j]的最小路径长度
        dp[0][0] = grid[0][0];
        int i = 1;
        int j = 1;
        //把第一行和第一列初始化,因为到他们的选择唯一
        for(i = 1;i < m;i ++){
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for(j = 1;j < n;j ++){
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }
        //循环完成dp[][]的赋值
        for(i = 1;i < m;i ++){
            for(j = 1;j < n;j ++){
                if(i == 0)
                    dp[i][j] = dp[i][j - 1] + grid[i][j];
                else if(j == 0)
                    dp[i][j] = dp[i - 1][j] + grid[i][j];
                else 
                    dp[i][j] = Math.min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j];
            }
        }
       
        return dp[m - 1][n - 1];
    }
}

class Solution {
    public int minPathSum(int[][] grid) {
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(i == 0 && j == 0) continue;
                else if(i == 0)  grid[i][j] = grid[i][j - 1] + grid[i][j];
                else if(j == 0)  grid[i][j] = grid[i - 1][j] + grid[i][j];
                else grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
            }
        }
        return grid[grid.length - 1][grid[0].length - 1];
    }
}

以上是优化版本,时间复杂度和空间复杂度都差不多。

94.不同的二叉搜索树

解法:动态规划,与树有关的动态规划要注意树根对解题思路的影响

class Solution {
    public int numTrees(int n) {
        //一维的动态规划问题
        //卡特兰数,二叉搜索树问题,这类问题不像之前的一维的动态规划问题,思路比较新颖
        //dp[0]-dp[n],因此是n + 1个
        if(n <= 1){
            return 1;
        }
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2;i <= n;i ++){
            for(int j = 1;j <= i;j ++){
                //注意是+=,j相当于在遍历树根所在的位置
                dp[i] += dp[j - 1]*dp[i - j];
            }
        }
        return dp[n];
    }
}

95.不同的二叉搜索树2

解法:递归的方法,二叉搜索树的根节点是1...n,因此当可能的根节点选择在其间时,自然兼顾了二叉搜索树的特点

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<TreeNode> generateTrees(int n) {
        List<TreeNode> ans = new ArrayList<> ();
        if(n == 0)
            return ans;
        return getAns(1,n);
    }
    private List<TreeNode> getAns(int start,int end){
        List<TreeNode> ans = new ArrayList<> ();
        //以下两个if条件是递归结束条件
        if(start == end){
            //如果是return ans.add(new TreeNode(start));不正确
            //原因是add()函数的返回值是boolean类型的
            //TreeNode tree = new TreeNode(start);
            ans.add(new TreeNode(start));
            return ans;
        }
        if(start > end){
            ans.add(null);
            return ans;
        }
        //尝试将i循环作为根节点,然后进行递归,
        //每一个根节点进行左右子树递归之后,需要将这个根节点的左子树和右子树添加到这个根节点上
        for(int i = start;i <= end;i ++){
            //犯的错误很浅显
            //List<TreeNode> left_trees = getAns(1,i - 1);
            List<TreeNode> left_trees = getAns(start,i - 1);
            List<TreeNode> right_trees = getAns(i + 1,end);
            //以下两种都是可以接受的
            //但是必须是嵌套循环,道理很简单根节点的编号是i,左子树集合和右子树集合是乘法关系(第94题)
            /*
            for(TreeNode left_tree : left_trees){
                for(TreeNode right_tree : right_trees){
                    TreeNode root_i = new TreeNode(i);
                    root_i.left = left_tree;
                    root_i.right = right_tree;
                    ans.add(root_i);
                }
            }
            */
            
            for(TreeNode left_tree : right_trees){
                for(TreeNode right_tree : left_trees){
                    TreeNode root_i = new TreeNode(i);
                    root_i.right = left_tree;
                    root_i.left = right_tree;
                    ans.add(root_i);
                }
            }
            
        }
        return ans;
    }
}

 

94.二叉树的中序遍历

解法1:递归

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<> ();
        helper(root,ans);
        return ans;
    }
    private void helper(TreeNode root,List<Integer> ans){
        if(root != null){
            if(root.left != null)
                helper(root.left,ans);
            ans.add(root.val);
            if(root.right != null)
                helper(root.right,ans);
        }
        
    }
}

解法2:利用栈进行非递归遍历

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<> ();
        TreeNode tree = root;
        Stack<TreeNode> stack = new Stack<>();
        while(tree != null || !stack.empty()){
            /*
            while(tree.left != null){
                stack.push(tree);
                tree = tree.left;
            }
            */
            //此处不应该是tree.left != null,因为tree先入的栈
            while(tree != null){
                stack.push(tree);
                tree = tree.left;
            }
            if( !stack.empty() ){
                tree = stack.pop();
                ans.add(tree.val);
                tree = tree.right;
            }

        }
        return ans;
    }
}

解法3:莫里斯遍历:能在时间复杂度为·O(n),空间复杂度为O(1)的情况下完成树的前序、中序、后序遍历。结合线索化二叉树的知识。莫里斯算法完成树的前序、中序、后序遍历的详细解法如下链接:https://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<> ();
        TreeNode curr = root;
        TreeNode pre = null;
        while(curr != null){
            if(curr.left == null){
                ans.add(curr.val);
                curr = curr.right;
            }
                
            else{
                pre = curr.left;
                while(pre.right != null && pre.right != curr)
                    pre = pre.right;
                //如果还没有完成中序遍历线索化,那就就完成中序遍历线索化:pre.right = curr;
                if(pre.right == null){
                    //完成中序遍历线索化
                    //让curr指针指向curr的左子树
                    pre.right = curr;
                    curr = curr.left;
                }else if(pre.right == curr){
                    //如果已经完成了线索化,证明curr的左子树已经遍历完毕,
                    //那么就恢复树原来的结构,并且将访问curr节点之后访问curr的右子树
                    pre.right = null;
                    ans.add(curr.val);
                    curr = curr.right;
                }
            }
        }
        return ans;
    }
}

莫里斯算法的中序遍历和前序遍历几乎相同,莫里斯遍历的后序遍历暂时不掌握,前序遍历如下:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<> ();
        TreeNode curr = root;
        TreeNode pre = null;
        while(curr != null){
            if(curr.left == null){
                ans.add(curr.val);
                curr = curr.right;
            }
                
            else{
                pre = curr.left;
                while(pre.right != null && pre.right != curr)
                    pre = pre.right;
                //如果还没有完成中序遍历线索化,那就就完成中序遍历线索化:pre.right = curr;
                if(pre.right == null){
                    //完成中序遍历线索化
                    //让curr指针指向curr的左子树
                    pre.right = curr;
                    //下面这一句是前序与中序的唯一区别
                    ans.add(curr.val);
                    curr = curr.left;
                }else if(pre.right == curr){
                    //如果已经完成了线索化,证明curr的左子树已经遍历完毕,
                    //那么就恢复树原来的结构,并且将访问curr节点之后访问curr的右子树
                    pre.right = null;
                    //ans.add(curr.val);
                    curr = curr.right;
                }
            }
        }
        return ans;
    }
}

62.不同路径

解法1:简单的二维动态规划,时间复杂度和空间复杂度都不是很好,也可以用排列组合的方法求解

class Solution {
    public int uniquePaths(int m, int n) {
        //二维动态规划问题
        if(m == 1 || n == 1)
            return 1;
        int[][] ans = new int[m][n];
        ans[0][0] = 1;
        for(int i = 0;i < m;i ++){
            for(int j = 0;j < n;j ++){
                if(i == 0 || j == 0){
                    ans[i][j] = 1;
                }
            }
        }
        for(int i = 1;i < m;i ++){
            for(int j = 1;j < n;j ++){
                ans[i][j] = ans[i][j - 1] + ans[i - 1][j];
            }
        }
        return ans[m - 1][n - 1];
    }
}

解法2:当做杨辉三角,用一维动态规划来求解

class Solution {
    public int uniquePaths(int m, int n) {
        //这是一个杨辉三角的问题,其中二维动态规划问题可以转化为一维动态规划的问题
        int[] ans = new int [n];
        Arrays.fill(ans,1);
        for(int i = 1;i < m;i ++){
            for(int j = 1;j < n;j ++){
                ans[j] += ans[j - 1];
            }
        }
        return ans[n - 1];
    }
}

63.不同的路径2

解法1:简单的二维动态规划问题,就是首先需要处理表示障碍的边界数组问题。时间复杂度可以接受,但是由于使用了二维数组导致空间复杂度太大。

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        if(obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1){
            return 0;
        }
        int[][] ans = new int[m][n];
        //首先改变obstacleGrid数组,让边界值等于1的后续边界处的值也等于1
        for(int i = 1;i < m;i ++){
            obstacleGrid[i][0] = (obstacleGrid[i][0] == 0 && obstacleGrid[i - 1][0] == 0) ? 0 : 1;
        }
        for(int i = 1;i < n;i ++){
            obstacleGrid[0][i] = (obstacleGrid[0][i] == 0 && obstacleGrid[0][i - 1] == 0) ? 0 : 1;
        }
        for(int i = 0;i < m;i ++){
            for(int j = 0;j < n;j ++){
                if(i == 0 || j == 0){
                    ans[i][j] = 1 - obstacleGrid[i][j];
                }else{
                    ans[i][j] = obstacleGrid[i][j] == 1 ? 0 : ans[i][j - 1] + ans[i - 1][j];
                }
            }
        }
        return ans[m - 1][n - 1];
    }
}

41.缺失的第一个正数

解法1:用Arrays.sort()函数进行排序之后,用count统计正数的个数,时间复杂度和空间复杂度都很好

class Solution {
    public int firstMissingPositive(int[] nums) {
        if(nums.length == 0)
            return 1;
        Arrays.sort(nums);
        if(nums[0] > 1 || nums[nums.length - 1] < 1)
            return 1;
        int count = 0;
        count = (nums[0] > 0) ? 1 : 0;
        for(int i = 1;i <= nums.length - 1;i ++){
            if(nums[i] > 0 && nums[i] != nums[i - 1]){
                count ++;
                if(count < nums[i]){
                    return count;
                }
            }
        }
        return (nums[nums.length - 1] > 0) ? nums[nums.length - 1] + 1 : 1;
    }
}

解法2:使用桶排序(鸽笼原理),难度不是很大

class Solution {
    //通过桶排序,让数组中的x尽可能地在nums[x - 1]上
    public int firstMissingPositive(int[] nums) {
        int len = nums.length;
        for(int i = 0;i < len;i ++){
            //这个是while循环而不是if条件的原因是可能替换后,还得继续替换的原因
            while(nums[i] > 0 && nums[i] < len + 1 && nums[nums[i] - 1] != nums[i]){
                swap(nums,nums[i] - 1,i);
            }
        }
        for(int i = 0;i < len;i ++){
            if((i + 1) != nums[i]){
                return i + 1;
            }
        }
        return len + 1;
    }
    private void swap(int[] nums,int first,int second){
        if(nums.length >= 2){
            try{
                int tem;
                tem = nums[first];
                nums[first] = nums[second];
                nums[second] = tem;
            }catch(Exception e){
                System.out.println("数组越界了");
            }
        }
    }
}

交换可以通过异或运算^来完成:实现原理https://blog.csdn.net/jocks/article/details/8182521

nums[index1] = nums[index1] ^ nums[index2];

nums[index2] = nums[index1] ^ nums[index2];

nums[index1] = nums[index1] ^ nums[index2];

33.搜索旋转数组

解法:此解法是利用题目中没有重复数据并且数组肯定发生旋转,来寻找left-mid-right之间单调递增的区域,要考两个判断条件来寻找

class Solution {
    //根据题意,这个数组一定发生了旋转,并且假设数组中存在不重复的元素
    //时间复杂度有要求, O(log n)
    //数组基本有序,因此使用二分查找
    public int search(int[] nums, int target) {
        //Arrays.sort(nums);
        if(nums.length == 0 || (nums.length == 1 && nums[0] != target))
            return -1;
        int len = nums.length;
        int left = 0;
        int right = nums.length - 1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] == target)
                return mid;
            /*
            if(nums[mid] > nums[right]){
                //nums[mid] > nums[right]表示left到mid之间递增,
                //因此下面一个条件句判断如果target在left到mid之间
                if(target >= nums[left] && target < nums[mid])
                    right = mid - 1;
                else
                    left = mid + 1;
            }else if(nums[mid] < nums[right]){
                //else if和else效果一样
                if(target > nums[mid] && target <= nums[right])
                    left = mid + 1;
                else
                    right = mid - 1;
            }
            */
            //如果有两个元素,参考地板除法,因此这个地方应该使用大于等于
            if(nums[mid] >= nums[left]){
                if(target >= nums[left] && target < nums[mid])
                    right = mid - 1;
                else 
                    left = left + 1;
            }else if(nums[mid] < nums[left]){
                if(target > nums[mid] && target <= nums[right])
                    left = mid + 1;
                else
                    right = mid - 1;
            }
        }
        return (nums[left] == target) ? left : -1;
    }
}

42.连续子数组的最大和

解法1:dp[i]表示以nums[i]结尾的连续子数组的和,那么转移方程可以如下程序所示

class Solution {
    public int maxSubArray(int[] nums) {
        //dp[i]表示以nums[i]结尾的子数组的和
        int n = nums.length;
        int ans = nums[0];
        if(n == 1){
            return ans;
        }
        int[] dp = new int[n];
        dp[0] = nums[0];
        for(int i = 1;i < n;i ++){
            if(nums[i] < 0){
                if(dp[i - 1] > 0)
                    dp[i] = nums[i] + dp[i - 1];
                else{
                    dp[i] = nums[i];
                }
            }else{
                if(dp[i - 1] < 0){
                    dp[i] = nums[i];
                }else
                    dp[i] = dp[i - 1] + nums[i];
            }
        }
        for(int j = 1;j < n;j ++){
            if(dp[j] > ans){
                ans = dp[j];
            }
        }
        return ans;
    }
}

解法2:从上面未精简的算法可以看出:当dp[i - 1] < 0时,dp[i] = nums[i]。

class Solution {
    public int maxSubArray(int[] nums) {
        //dp[i]表示以nums[i]结尾的子数组的和
        //当dp[i - 1] < 0时,dp[i] = nums[i] + 0
        //当dp[i - 1] >= 0时,dp[i] = dp[i - 1] + nums[i]
        int ans = nums[0];
        for(int i = 1;i < nums.length;i ++){
            nums[i] += Math.max(nums[i - 1],0);
            ans = Math.max(ans,nums[i]);
        }
        return ans;
    }
}

24.反转链表

解法:头插法,代码如下:

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null)
            return head;
        ListNode result = new ListNode(0);
        result.next = null;
        ListNode p = head;
        ListNode tem;
        while(p != null){
            tem = p;
            //注意:p = p.next;
            //应该写到tem的next改变之前,因为tem和p指向同一个对象
            p = p.next;
            tem.next = result.next;
            result.next = tem;
        }
        return result.next;
    }
}

148.排序链表

解法1:简单选择排序(时间复杂度和空间复杂度都很低)

class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null || head.next == null)
            return head;
        ListNode ans = new ListNode(0);
        ListNode current = head;
        //引入after的原因是java传递对象相当于c的传引用,会导致current的next
        //被修改
        ListNode after = current.next;
        while(current.next != null){
            ans = sort(current,ans);
            current = after;
            after = after.next;
        }
        return sort(current,ans).next;
    }
    public ListNode sort(ListNode current,ListNode ans){
        if(ans.next == null){
            ans.next = current;
            ans.next.next = null;
            return ans;
        }
        ListNode pre = ans;
        ListNode tem = ans.next;
        while(tem != null){
            if(tem.val >= current.val){
                current.next = pre.next;
                pre.next = current;
                return ans;
            }
            pre = tem;
            tem = tem.next;
        }
        current.next = pre.next;
        pre.next = current;
        return ans;
    }
}

解法2:归并排序,空间复杂度不是很好

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        if(head == null || head.next == null)
            return head;
        ListNode ans = head;
        ans = merge_sort(head);
        return ans;
    }
    //使用归并排序对单链表进行排序
    public ListNode merge_sort(ListNode ans){
        if(ans == null || ans.next == null)
            return ans;
        ListNode[] listnodes = divide(ans);
        ListNode left = merge_sort(listnodes[0]);
        ListNode right = merge_sort(listnodes[1]);
        return merge(left,right);
    }
    //合并两个有序单链表
    public static ListNode merge(ListNode curr_left,ListNode curr_right){
        ListNode ans = new ListNode(-1);
        ListNode curr = ans;
        while(curr_left != null && curr_right != null){
            if(curr_left.val < curr_right.val){
                curr.next = curr_left;
                curr_left = curr_left.next;
            }else{
                curr.next = curr_right;
                curr_right = curr_right.next;
            }
            curr = curr.next;
        }
        curr.next = curr_left == null ? curr_right : curr_left;
        
        return ans.next;
    }
    public static ListNode[] divide(ListNode ans){
        ListNode current = ans;
        ListNode[] listnodes = new ListNode[2];
        if(ans == null){
            return listnodes;
        }
        ListNode ans1 = null;
        ListNode ans2 = null;
        int left =1;
        int right = 0;
        while(current != null){
            right ++;
            current = current.next;
        }
        current = ans;
        int mid = (left + right) / 2;
        while(current != null){
            
            if(left == mid){
                ans1 = ans;
                ans2 = current.next;
                current.next = null;
                break;
            }
            current = current.next;
            left ++;
        }
        /*
        current = ans1;
        while(current != null){
            System.out.println(current.val);
            current = current.next;
        }
        */
        listnodes[0] = ans1;
        listnodes[1] = ans2;
        return listnodes;
    }
}

21.合并两个有序单链表

解法:如果是倒序,那么就用头插法,否则是尾插法。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode curr = new ListNode(0);
        ListNode ans = curr;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                ans.next = l1;
                l1 = l1.next;
            }else{
                ans.next = l2;
                l2 = l2.next;
            }
            ans = ans.next;
        }
        ans.next = l1 == null ? l2 : l1;
        return curr.next;
    }
}

1013将数组分为和相等的三部分

解法:双指针,这是解决数组问题的基本思路

class Solution {
    public boolean canThreePartsEqualSum(int[] A) {
        if(A.length < 3){
			return false;
		}else{
			int left = 0;
			int right = A.length - 1;
			int sum = 0;
			for(int i = 0;i <= right;i ++){
				sum += A[i];
			}
			if((sum % 3) != 0){
				return false;
			}else{
				int sum_left = A[0];
				int sum_right = A[right];
				//因为是分成三部分,因此条件是:left + 1 < right
				while(left + 1 < right){
					//判断如果左右指针都符合要求,那么就返回正确
                    if((sum_right == sum / 3) && (sum_left == sum / 3)){
						return true;
					}
					//如果左指针不符合要求就向右移动左指针,否则不动
                    if(sum_left != sum / 3){
						sum_left += A[++ left];
					}
                    //如果右指针不符合要求就向左移动右指针,否则不动
					if(sum_right != sum / 3){
						sum_right += A[-- right];
					}
				}
				return false;
			}
		}
    }
}

1616.部分排序

解法:采用整体排序的思想,先把数组深拷贝,然后从两端进行比较,数据不一样的即为边界

class Solution {
    public int[] subSort(int[] array) throws NullPointerException{
		int[] ans = new int[]{-1,-1};
		if(array.length < 0){
			throw new NullPointerException();
		}else if(array.length == 1){
			return ans;
		}else{
			//这个题的关键思路就在先深拷贝数组,然后对它进行排序
            int[] copy = Arrays.copyOf(array, array.length);
            Arrays.sort(copy);
			int left = 0;
			int right = array.length - 1;
			boolean flag1 = false;
			boolean flag2 = false;
			while(left < right){
				if(array[left] == copy[left]){
					left ++;
				}else{
					flag1 = true;
				}
				if(array[right] == copy[right]){
					right -- ;
				}else{
					flag2 = true;
				}
				if(flag1 && flag2){
					break;
				}
			}
			if(left < right){
				ans[0] = left;
				ans[1] = right;
				
			}
            
		}
		return ans;
	}
}

1011.在D天内送达包裹的能力

解法:考虑二分查找的思想,right = 重量和,left =重量的最大值。由于要求的是D天内送达的最小装载能力。因此采用二分左限界查找。也就是说,即使mid满足条件,也不返回,让right = mid,当mid不满足条件,让left = mid + 1。最后返回left。

class Solution {
    //二分查找左限界法
    public int shipWithinDays(int[] weights, int D) {
        if(weights.length <= 0){
        	return -1;
        }
        int max = 0;
        int min = weights[0];
        for(int i : weights){
        	max += i;
        	min = Math.max(min, i);
        }
        while(min < max){
        	int mid = min + (max - min) / 2;
        	//区间变为[left,mid]
        	if(judge(weights,D,mid)){
        		max = mid;
        	}else{
        		min = mid + 1;
        	}
        }
        return min;
    }
	private boolean judge(int[] weights,int D,int weight){
		int consist = weight;
		for(int i = 0;i < weights.length;i ++){
			
            if(consist < weights[i]){
				return false;
			}
            
            if(weight - weights[i] < 0){
				weight = consist - weights[i];
				D --;
			}else{
				weight -= weights[i];
			}
		}
		return D > 0;
	}
}

875爱吃香蕉的猴子

解法:和上题类似,采用的是左限界的二分查找,left =  piles数组最小的数,right = piles数组最大的数。

class Solution {
    public int minEatingSpeed(int[] piles, int H) {
		if(piles.length < 0){
			throw new NullPointerException();
		}
		int max = 0;
		int min = 0;
		for(int i = 0;i < piles.length;i ++){
			if(min > piles[i]){
				min = piles[i];
			}
			if(max < piles[i]){
				max = piles[i];
			}
		}
		//待求解的是最小速度,因此采用左限界二分查找
		while(min < max){
			int mid = min + (max - min) / 2;
			if(judgeMinEatingSpeed(piles, H, mid)){
				max = mid;
			}else{
				min = mid + 1;
			}
		}
		return min;
    }
	public boolean judgeMinEatingSpeed(int[] piles,int H,int ans){
		if(ans == 0){
            return false;
        }
        int time = 0;
		for(int i = 0;i < piles.length;i ++){
			if(piles[i] == 0){
				time ++;
			}else{
                if(piles[i] % ans == 0){
                    time += piles[i] / ans;
                }else{
                    time += (piles[i] / ans) + 1;
                }
            }
		}
		if(H >= time){
			return true;
		}else{
			return false;
		}
	}
}

 171. Excel表列序号

class Solution {
    public int titleToNumber(String s) {
		if(s == null && s.length() == 0){
			return 0;
		}
		int ans = 0;
		for(int i = 0;i < s.length();i ++){
			if(s.charAt(i) >= 'A' && s.charAt(i) <= 'Z'){
				int value = s.charAt(i) - 'A' + 1;
				//第i个数乘i - 1次
                ans = ans * 26 + value;
			}else{
				throw new IllegalArgumentException();
			}
		}
		return ans;
    }
}

 168. Excel表列名称

解法:26进制转10进制,需要注意的是1对应A,但是0没有对应,因此在输入的数大于26的时候要进行n --

class Solution {
    public String convertToTitle(int n) {
        if(n < 0){
			throw new IllegalArgumentException();
		}else if(n == 0){
			return "";
		}else{
			StringBuilder sb = new StringBuilder();
			Stack<Character> stack = new Stack<>();
			while(n > 26){
				//原本这个n --没有,但是由于0没有字母对应,因此先进进行n --
                n --;
                int m = n % 26;
				//stack.push((char)('A' + m - 1));
                stack.push((char)('A' + m));
				n = n / 26;
			}
			if(n > 0){
                
                stack.push((char)('A' + n - 1));
			}
			
			while( ! stack.isEmpty()){
				sb.append(stack.pop());
			}
			return sb.toString();
		}
    }
}

415. 字符串相加

解法:模拟进位,从低位向高位相加即可。

class Solution {
    public String addStrings(String num1, String num2) {
        if((num1 == null && num1 == "") || (num2 == null && num2 == "")){
        	return null;
        }
		StringBuilder sb = new StringBuilder();
		StringBuilder ans = new StringBuilder();
        int index1 = num1.length() - 1;
        int index2 = num2.length() - 1;
        int add = 0;
		while(index1 >= 0 && index2 >= 0){
			//把char转化为int
			int number1 = Character.getNumericValue(num1.charAt(index1));
			int number2 = Character.getNumericValue(num2.charAt(index2));
			int num = number1 + number2 + add;
			add = num / 10;
			//StringBulider可以直接追加int
			sb.append(num % 10);
			index1 --;
			index2 --;
        }
		while(index1 >= 0){
			int number1 = Character.getNumericValue(num1.charAt(index1));
			int num = number1 + add;
			add = num / 10;
			//StringBulider可以直接追加int
			sb.append(num % 10);
			index1 --;
		}
		while(index2 >= 0){
			int number2 = Character.getNumericValue(num2.charAt(index2));
			int num = number2 + add;
			add = num / 10;
			//StringBulider可以直接追加int
			sb.append(num % 10);
			index2 --;
		}
		if(add > 0){
			sb.append(add);
		}
		for(int i = sb.length() - 1;i >= 0;i --){
			ans.append(sb.charAt(i));
		}
		return ans.toString();
    }
}

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值