刷题冲冲冲

算法思想

无算法hhh

两数相加

给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

思路:

没看题解之前,思路就是对的,从前往后依次加两个列表的值,并有一个记录进位的变量。

但有一些小细节没有考虑到(最后一直进1,res会再增加一位)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head1 = l1;
        ListNode head2 = l2;
        ListNode result = new ListNode(0);
        ListNode res = result;
        int plu = 0;
        while(head1 != null && head2 != null){
            int current = head1.val + head2.val+plu;
            if(current>=10){
                res.next = new ListNode(current%10);
                plu = 1;
                res = res.next;
            }
            else{
                res.next = new ListNode(current);
                plu = 0;
                res = res.next;
            }
            head1 = head1.next;
            head2 = head2.next;
        }
        if(head1 != null){
            while(head1 != null){
                int current = head1.val+plu;
                if(current>=10){
                    res.next = new ListNode(current%10);
                    plu = 1;
                }
                else{
                    res.next = new ListNode(current);
                    plu = 0;
                }
                res = res.next;
                head1 = head1.next;
            }
        }
        if(head2 != null){
            while(head2 != null){
                int current = head2.val+plu;
                if(current>=10){
                    res.next = new ListNode(current%10);
                    plu = 1;
                }
                else{
                    res.next = new ListNode(current);
                    plu = 0;
                }
                res = res.next;
                head2 = head2.next;
            }
        }
        if(plu==1){
            res.next = new ListNode(1);
        }
        return result.next;
    }
}

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

输入:strs = ["flower","flow","flight"]
输出:"fl"

思路一:横向扫描,时间复杂度O(mn)

先写一个函数,求两个字符串的公共前缀。然后第一个和第二个求公共前缀,得到的结果和第三个求,依次往后

StringBuilder sb = new StringBuilder();
sb.append(item);
sb.toString();
//获取字符串的第i的字符
s.charAt(i)
//得到字符串的子字符串
s.substring(startIndex, endIndex)

思路二:纵向扫描,时间复杂度O(mn)

两层循环,外层为第一个字符串的长度,内层为数组的长度。退出循环的条件:字符串长度==外层遍历||字符串[index]!=第一个字符串[index]

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0){
            return "";
        }
        //纵向扫描
        for(int index = 0; index<strs[0].length(); index++){
            for(int i=1; i<strs.length; i++){
                if(strs[i].length()==index){
                    return strs[0].substring(0, index);
                }
                if(strs[i].charAt(index) != strs[0].charAt(index)){
                    return strs[0].substring(0, index);
                }
            }
        }
        return strs[0];
    }
}

思路三:分治法,感觉也可以叫二分法,时间复杂度更高,没看了

数组中的第K大元素

思路一:冒泡排序

每次冒出最小的元素,这样冒k次,就得到了结果

思路二:快选

思路三:优先队列

优先级队列中,数据按关键词有序排列,插入新数据的时候,会自动插入到合适的位置保证队列有序。(顺序有两种形式:升序或者是降序)

PriorityQueue<Integer> minHeap = new PriorityQueue<>();
minHeap.add(num);//添加元素
minHeap.peek();//返回堆顶但不删除
minHeap.poll();//返回堆顶且删除堆顶
minHeap.indexOf(Object o);//查询对象o的索引
minHeap.contain(Object o);//判断是否包含o
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<>(nums.length);
        for(int i=0; i<nums.length; i++){
            pq.add(nums[i]);
        }
        for(int i=0; i<nums.length-k; i++){
            pq.poll();
        }
        return pq.peek();
    }
}
//优化一下,维护一个长度为k的堆,先把堆填满,然后如果新的数组元素大于堆的最小元素,就弹出堆,新元素加入堆
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<>(nums.length);
        for(int i=0; i<nums.length; i++){
            if(pq.size()<k){
                pq.add(nums[i]);
            }
            else{
                if(nums[i]>pq.peek()){
                    pq.poll();
                    pq.add(nums[i]);
                }
            }
        }
        return pq.peek();
    }
}

字符串相加

给定两个字符串形式的非负整数 num1num2 ,计算它们的和。

//怎么把char的数字转成int
char a = '1';
int s = a - '0';
//StringBuilder添加、转成String
StringBuilder ss = new StringBuilder();
ss.append(1);//可以直接添加int
ss.append(2);
ss.reverse().toString();//转成String
//怎么判断String类型的字符串是不是0
String s = "0";
if(s.equals("0"));
class Solution {
    public String addStrings(String num1, String num2) {
        int index1 = num1.length()-1;
        int index2 = num2.length()-1;
        int current1 = 0;
        int current2 = 0;
        int plus = 0;
        StringBuilder ss = new StringBuilder("");
        while(index1>=0||index2>=0){
            current1 = index1>=0 ? num1.charAt(index1)-'0' : 0;
            current2 = index2>=0 ? num2.charAt(index2)-'0' : 0;
            int sum = current1+current2+plus;
            if(sum/10>0){
                plus = 1;
                ss.append(sum%10);
            }
            else{
                plus = 0;
                ss.append(sum%10);
            }
            index1--;
            index2--;
        }
        if(plus == 1){
            ss.append(1);
        }
        return ss.reverse().toString();
    }
}

字符串相乘

给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。

输入: num1 = "2", num2 = "3"
输出: "6"

思路一:定义两个函数,一个实现字符串和char相乘,另一个实现两个字符串相加。num1和num2的每一位相乘,注意补0的情况,然后每乘一位,就和res相加。

思路二:乘数 num1 位数为 MM,被乘数 num2 位数为 NN, num1 x num2 结果 res 最大总位数为 M+N。num1[i] x num2[j] 的结果为 tmp(位数为两位,"0x","xy"的形式),其第一位位于 res[i+j],第二位位于 res[i+j+1]

class Solution {
    public String multiply(String num1, String num2) {
        if (num1.equals("0") || num2.equals("0")) {
            return "0";
        }
        int num1Len = num1.length();
        int num2Len = num2.length();
        int[] res = new int[num1Len + num2Len];
        int currentSum = 0;
        for(int i=num1Len-1; i>=0; i--){
            for(int j=num2Len-1; j>=0; j--){
                currentSum = (num1.charAt(i)-'0') * (num2.charAt(j)-'0') + res[i+j+1];
                res[i+j+1] = currentSum%10;
                res[i+j] += currentSum/10;
            }
        }
        int index = 0;
        while(res[index]==0){
            index++;
        }
        StringBuilder rs = new StringBuilder();
        for(;index<res.length; index++){
            rs.append(res[index]);
        }
        return rs.toString();
    }
}

旋转数组

给定一个数组,将数组中的元素向右移动 k个位置,其中 k 是非负数。

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]

思路:连续翻转,思路很巧妙,单独写一个数组翻转的函数,可以指定开始结束位置的,然后先翻转nums,再翻转nums[0~k-1],再翻转nums[k,len]

注意对k的处理,k%nums.length

class Solution {
    public void rotate(int[] nums, int k) {
        k = k%nums.length;
        nums = reverse(nums, 0, nums.length-1);
        nums = reverse(nums, 0, k-1);
        nums = reverse(nums, k, nums.length-1);
    }
    public int[] reverse(int[] a, int start, int end){
        //翻转指定位置的数组元素
        while(start<end){
            int temp = a[start];
            a[start] = a[end];
            a[end] = temp;
            start++;
            end--;
        }
        return a;
    }
}

数组加一

给定一个由 整数组成的 非空数组所表示的非负整数,在该数的基础上加一。

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。

思路:需要考虑加1会产生进位的情况,从后往前遍历元素,先加一,然后%10看是否为0,如果是0就继续循环,不是返回digits,只有9999才会产生加一位的情况

class Solution {
    public int[] plusOne(int[] digits) {
        for(int i=digits.length-1; i>=0; i--){
            digits[i]++;
            digits[i] = digits[i]%10;
            if(digits[i]!= 0){
                return digits;
            }
        }
        digits = new int[digits.length+1];
        digits[0] = 1;
        return digits;
    }
}

简化路径

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径。

一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//')都被视为单个斜杠 '/' 。

输入:path = "/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。 
输入:path = "/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。

思路:

​ 定义一个ArrayList来存放结果,因为添加删除最后一个元素比较方便

​ 先按照"/"划分字符串

​ 遍历划分后的字符串数组,如果是“..”,就从ArrayList弹出最后一个,如果 是普通路径(非“.” || 非“”)就加入ArrayList

​ 按照/合并ArrayList

//ArrayList和LinkedList的选择
使用ArrayList:频繁访问列表中的一个元素;只需要在列表末尾进行添加和删除元素的操作。
使用LinkedList:需要通过循环迭代来访问列表中的某些元素;需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素的操作。
ArrayList的方法:
ArrayList<String> a = new ArrayList<>();
a.add("a");//添加在最后
a.remove(int index)//删除
a.get(int index);//获取元素
a.set(index, s);//修改元素
a.size();//获取列表的长度
LinkedList方法:
LinkedList<String> l = new LinkedList<>();
l.add("a");//添加在最后
l.addFirst("a");//添加在最前面
l.addLast("a");//添加在最后
l.removeFirst();//删除
l.removeLast();
l.getFirst();//获取
l.getLast();
l.size();//获取列表的长度
//String的split
s.split("/");//注意里面是字符串
访问列表中的 字符串,很奇怪不能用==,要用.equals
String[] pathList = path.split("/");
if(pathList[i].equals(".."))
class Solution {
    public String simplifyPath(String path) {
        //先按照split划分
        String[] pathList = path.split("/");
        ArrayList<String> res = new ArrayList();
        for(int i=0; i<pathList.length; i++){
            if(pathList[i].equals("..")){
                if(res.size()>=1){
                    res.remove(res.size()-1);
                }
            }
            else if(!pathList[i].equals(".") && !pathList[i].equals("")){
                res.add(pathList[i]);
            }
        }
        StringBuilder ss = new StringBuilder("/");
        for(int i=0; i<res.size(); i++){
            ss.append(res.get(i));
            if(i != res.size()-1){
                ss.append("/");
            }
        }
        return ss.toString();
    }
}

判断回文数

思路:先把整数一次取最后一位,保存在一个数组里,然后再用两个指针,判断是不是回文数--自己想的hhh

public boolean isPalindrome(int x) {
        //把x存在数组里,然后两个指针遍历数组
        if(x<0){return false;}
        ArrayList<Integer> lis = new ArrayList<>();
        while(x!=0){
            lis.add(x%10);
            x = x/10;
        }
        int left = 0;
        int right = lis.size()-1;
        while(left<=right){
            if(lis.get(left)!=lis.get(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

思路二:只翻转一半比较即可,注意循环结束的条件是x>reverseX

public boolean isPalindrome(int x) {
        //翻转一半的数字
        if(x<0||(x%10==0&&x>0)){return false;}
        int reverseX = 0;
        while(x>reverseX){//这里很重要
            int cur = x%10;
            reverseX = reverseX*10 + cur;
            x = x/10;
        }
        if(x==reverseX||x==reverseX/10){
            return true;
        }
        else{return false;}
    }

字符串转整数

public int myAtoi(String str) {
        int len = str.length();
        // str.charAt(i) 方法回去检查下标的合法性,一般先转换成字符数组
        char[] charArray = str.toCharArray();
        // 1、去除前导空格
        int index = 0;
        while (index < len && charArray[index] == ' ') {
            index++;
        }
        // 2、如果已经遍历完成(针对极端用例 "      ")
        if (index == len) {
            return 0;
        }
        // 3、如果出现符号字符,仅第 1 个有效,并记录正负
        int sign = 1;
        char firstChar = charArray[index];
        if (firstChar == '+') {
            index++;
        } else if (firstChar == '-') {
            index++;
            sign = -1;
        }
        // 4、将后续出现的数字字符进行转换
        // 不能使用 long 类型,这是题目说的
        int res = 0;
        while (index < len) {
            char currChar = charArray[index];
            // 4.1 先判断不合法的情况
            if (currChar > '9' || currChar < '0') {
                break;
            }
            // 题目中说:环境只能存储 32 位大小的有符号整数,因此,需要提前判:断乘以 10 以后是否越界
            if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)) {
                return Integer.MAX_VALUE;
            }
            if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))) {
                return Integer.MIN_VALUE;
            }
            // 4.2 合法的情况下,才考虑转换,每一步都把符号位乘进去
            res = res * 10 + sign * (currChar - '0');
            index++;
        }
        return res;
    }

二维数组中的查找

判断二维数组中是否有目标值,数组每一行和每一列都是递增的

思路:外层循环遍历每一行,c从最右边,如果当前元素大于目标值,c--,小于r++

public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0){
            return false;
        }
        int r = 0, c = matrix[0].length - 1;
        while(r <= matrix.length - 1 && c >= 0){
            if (matrix[r][c] == target){
                return true;
            }
            else if (matrix[r][c] > target){
                c--;                
            }
            else if (matrix[r][c] < target){
                r++;
            }
        }
        return false;
    }

哈希表

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

输入:nums = [2,7,11,15], target = 9
输出:[0,1]

思路:

方法一:暴力两层循环

方法二:利用hashtable,这样省去了一层循环,只需要在hashtable里找有没有target-num[i]

HashMap<Integer, Integer> tb = new HashMap<>();
tb.containsKey(target);
tb.get(k); 获取k对应的v
tb.put(k, v);
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        HashMap<Integer, Integer> tb = new HashMap<>();
        for(int i = 0; i<nums.length; i++){
            if(tb.containsKey(target-nums[i])){
                res[0] = i;
                res[1] = tb.get(target-nums[i]);
                return res;
            }
            else{
                tb.put(nums[i], i);
            }
        }
        return res;
    }
}

有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

输入:s = "()"
输出:true

首先判断s的长度是不是偶数个,不是直接返回false。利用栈+HashMap来解决,遍历字符串s,如果s[i]不在字典的key里,就进栈,如果在,判断栈顶元素是不是字典中s[i]的value,不是就返回false,栈为空也返回false,最后结果返回栈是否为空

//定义一个栈
Deque<Character> ch = new LinkedList<Character>();
//栈的操作
ch.push(i);//进栈
ch.peek();//返回栈顶元素
ch.pop();//返回栈顶元素,并移除
ch.isEmpty();//判断栈是否为空
class Solution {
    public boolean isValid(String s) {
        int len = s.length();
        if(len%2!=0){
            return false;
        }
        HashMap hb = new HashMap();
        hb.put('}', '{');
        hb.put(']', '[');
        hb.put(')', '(');
        Deque<Character> dp = new LinkedList<>();
        for(int i=0; i<len; i++){
            char cur = s.charAt(i);
            if(!hb.containsKey(cur)){
                dp.push(cur);
            }
            else{
                if(dp.isEmpty()){return false;}
                if(hb.get(cur) == dp.peek()){dp.poll();}
                else{return false;}
            }
        }
        if(dp.isEmpty()){return true;}
        else{return false;}
    }
}

有效的字母异位词

给定两个字符串 s和 t,编写一个函数来判断 t 是否是s的字母异位词。

输入: s = "anagram", t = "nagaram"
输出: true

思路一:异位词说明每个元素的个数是相同的。利用一个数组统计每个字符的个数,数组长度为26,遍历s,每个元素位置上加1,再遍历t,每个元素位置上减1,数组出现小于0返回false

思路二:st转化为数组,然后对数组排序,如果排序后的数组相同,说明是字母异位词

//String 转成char[]
char[] ss = s.toCharArray();
//数组排序
Arrays.sort();
//判断两个数组是不是值相等
Arrays.equals(ss,tt);
class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length()!=t.length()){
            return false;
        }
        char[] ss = s.toCharArray();
        char[] tt = t.toCharArray();
        Arrays.sort(ss);
        Arrays.sort(tt);
        return Arrays.equals(ss,tt);
    }
}

字母异位词分组

给你一个字符串数组,请你将字母异位词组合在一起。可以按任意顺序返回结果列表。

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

思路:创建一个字典,字典的key是字符串排序后的结果,value是字符串排序后相同元素的列表,最后返回字典的values即可

//创建一个字典,key格式为String,value格式为List
HashMap<String, ArrayList<String>> mp = new HashMap<>();
//怎么更新v呢?先把v取出来,添加新元素,再把v放回去
ArrayList<String> lis = mp.get(k);
lis.add(newS);
mp.put(k, lis);
//返回字典的values
return new ArrayList<ArrayList<String>>(mp.values());
//char[]怎么转成String
char[] cc = new char[]{'a', 'b', 'c'};
String ss = new String(cc);
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String, ArrayList<String>> hm = new HashMap<>();
        for(int i=0; i<strs.length; i++){
            String current = strs[i];
            char[] cc = current.toCharArray();
            Arrays.sort(cc);
            String after = new String(cc);
            if(hm.containsKey(after)){
                ArrayList lis = hm.get(after);
                lis.add(current);
                hm.put(after, lis);
            }
            else{
                ArrayList lis = new ArrayList<String>();
                lis.add(current);
                hm.put(after, lis);
            }
        }
        return new ArrayList<List<String>>(hm.values());
    }
}

双指针

盛最多水的容器

双指针做,每次移动较小值的那个指针

class Solution {
    public int maxArea(int[] height) {
        //双指针
        int left = 0;
        int right = height.length-1;
        int maxRes = Integer.MIN_VALUE;
        while(left<right){
            int current = Math.min(height[left], height[right])*(right-left);
            maxRes = Math.max(maxRes, current);
            //移动较小的指针
            if(height[left] > height[right]){
                right--;
            }
            else{
                left++;
            }
        }
        return maxRes;
    }
}

移动0

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

思路:两个指针lr指向开始位置,保证l左边都是排好的,移动r遍历数组,当r!=0的时候,交换lr位置的值,l++

class Solution {
    public void moveZeroes(int[] nums) {
        int left = 0;
        int right = 0;
        while(right < nums.length){
            if(nums[right]!= 0){
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                left++;
            }
            right++;
        }
    }
}

移动元素

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

思路:双指针,保证l的右边都是0--自己做出来的hhh

public int removeElement(int[] nums, int val) {
        int len = nums.length;
        int l = len-1, r = len-1;
        while(r>=0){
            if(nums[r]==val){
                nums[r] = nums[l];
                nums[l] = 0;
                r--;
                l--;
            }
            else{r--;}
        }
        return l+1;
    }

合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

自己写代码实现是两个指针遍历两个链表做的,结果看到用递归做的,绝绝子,但其实没看懂,估计下次遇到类似的题目还是不会做。

递归的思想:首先递归结束的条件是当其中一个链表为空时,就返回另一个链表。那递归判断 l1l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        if(l1.val<l2.val){
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }
        else{
            l2.next = mergeTwoLists(l2.next, l1);
            return l2;
        }
    }
}

合并两个有序数组

给你两个有序整数数组nums1 和 nums2,请你将 nums2合并到 nums1 中,使 nums1成为一个有序数组。

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]

思路:首先想一下怎么能不建立新的数组,直接在num1上操作,分别定义两个指针指向两数组最后,从后往前遍历数组,选择较大的值放在num1的最后,并往前移动指针。考虑最后的情况,p1<0或p2<0的时候,直接移动那个>=0的指针

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //从后往前
        int p1 =  m-1;
        int p2 = n-1;
        int tail = m+n-1;
        int cur = 0;
        while(p1>= 0 || p2>= 0){
            if(p1<0){
                cur = nums2[p2];
                p2--;
            }
            else if(p2<0){
                cur = nums1[p1];
                p1--;
            }
            else if(nums1[p1]>nums2[p2]){
                cur = nums1[p1];
                p1--;
            }
            else{
                cur = nums2[p2];
                p2--;
            }
            nums1[tail] = cur;
            tail--;   
        }
    }
}

无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

思路:用滑动窗口,两个指针left、right实现滑动窗口,定义一个HashMap,放(s[i], i),只需要遍历一遍s,如果s[i]在HashMap的key里面,left要移动到这个位置的后一个,每次都把s[i]放进Map里,求长度取最大值。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length()==0){
            return 0;
        }
        int left = 0;
        int res = 0;
        HashMap<Character, Integer> mp = new HashMap<>();
        for(int i=0; i<s.length(); i++){
            if(mp.containsKey(s.charAt(i))){
            	//下面这句话,注意要取较大值
               left = Math.max(left, mp.get(s.charAt(i))+1);
            }
            mp.put(s.charAt(i), i);
            res = Math.max(res, i-left+1);
        }
        return res;
    }
}

字符串的排列

给你两个字符串 s1s2 ,写一个函数来判断 s2 是否包含 s1 的排列。

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

思路一:滑动窗口,定义一个长度为26的数组,记录s1每个字符出现的次数,然后长度为s1Len窗口在s2滑动,统计滑动窗口内的数组是否和s1的那个数组相同

//判断两个数组是否相同
int[] a = {1, 2};
int[] b = {1, 2};
Arrays.equals(a, b);

思路二:双指针,只是不需要两个数组每次都进行equals对比,只用一个数组cnt记录,先遍历s1,数组cnt减操作。再移动right和left指针,right+1,cnt加操作,left+1,cnt减操作,当r-l==s1.lenght()-1就返回true

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        if(s1.length()>s2.length()){
            return false;
        }
        int[] cnt = new int[26];
        for(int i=0; i<s1.length(); i++){
            cnt[s1.charAt(i)-'a']--;
        }
        int left = 0;
        int right = 0;
        while(right<s2.length()){
            int x = s2.charAt(right) - 'a';
            cnt[x]++;
            while(cnt[x]>0){
                //移动左指针
                cnt[s2.charAt(left)-'a']--;
                left++;
            }
            if(right-left==s1.length()-1){
                return true;
            }
            right++;
        }
        return false;
    }
}

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

思路:

特判,对于数组长度 nn,如果数组为 null或者数组长度小于 3,返回 [][]。
对数组进行排序。
遍历排序后数组:
若 nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于 00,直接返回结果。
对于重复元素:跳过,避免出现重复解
令左指针 L=i+1,右指针 R=n-1,当 L<R 时,执行循环:
当 nums[i]+nums[L]+nums[R]等于0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R 移到下一位置,寻找新的解
若和大于 0,说明 nums[R] 太大,R左移
若和小于 0,说明 nums[L] 太小,L右移

//结果为二维列表, [[-1,-1,2],[-1,0,1]]
List<List<Integer>> res = new ArrayList<>();
res.add(Arrays.asList(i1, i2, i3));
//一维整数列表排序
Arrays.sort(nums);
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //指针法
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        int len = nums.length-1;
        for(int i=0; i<len-2; i++){
            if(nums[i]>0){
                break;
            }
            if(i>0){
                if(nums[i] == nums[i-1]){
                    continue;
                }
            }
            int left = i+1; 
            int right = len;
            while(left<right){
                int currentSum = nums[i] + nums[left] + nums[right];
                if(currentSum==0){
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while(left<right&&nums[right-1]==nums[right]){right--;}
                    while(left<right&&nums[left+1]==nums[left]){left++;}
                    left++;
                    right--;
                }
                else if(currentSum>0){
                    right--;
                }
                else{left++;}  
            } 
        }
        return res;
    }
}

环形链表

给定一个链表,判断链表中是否有环。

思路:双指针,定义快慢指针,如果有环,那么快慢指针一定会相遇。

快慢指针都指向head,while(fast!=null&&fast.next!=null),先移动指针,再比较值

环形链表II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回null。

首先双指针,判断有没有环,快慢指针相遇时表示有环,相遇后fast指向链表开始位置,快慢指针再次相遇时,就是环入口。

关键是推导出(相遇点到环入口的距离=链表开始到环入口的距离):

a+b+n(b+c)=2(a+b)⟹a=c+(n−1)(b+c)

例如快指针走过的长度a+b+c+b,慢指针走过的长度a+b,前者等于后者*2,就推导出a=c

为什么慢指针在第一圈一定能和快指针相遇?fast指针走的路程一直是slow指针的二倍,所以当slow指针走环形走一半时,fast指针走的路程一定大于等于一圈,所以当slow指针走一半或者一半以前两者必定相遇

public class Solution {
    public ListNode detectCycle(ListNode head) {
        //快慢指针
        if(head==null||head.next==null){
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while(fast!= null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                //有环
                fast = head;
                while(fast!=slow){
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }
}

删除有序数组中的重复项

给你一个有序数组 nums,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

思路:hhhh没有看题解一次就做出来了,用两个指针,初始时left=0,right=1,left左边放排好序的数,right遍历数组,当左右指针的数不同时,交换两个数,左指针右移。

class Solution {
    public int removeDuplicates(int[] nums) {
        int left = 0;
        int right = 1;
        while(right<nums.length){
            if(nums[right] != nums[left]){
                left++;
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
            }
            right++;
        }
        return left+1;
    }
}

翻转字符串里的单词

给你一个字符串 s ,逐个翻转字符串中的所有 单词

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

输入:s = "  hello world  "
输出:"world hello"
解释:输入字符串可以在前面或者后面包含多余的空格,但是翻转后的字符不能包括。

思路一:天,自己竟然做出来了,但时间复杂度和空间复杂度都很高。用栈实现,先把s压入栈,注意一开始进栈的时候,排除开始的空格,然后一次出栈,遇到空格就字符串反转然后加到结果中

//把StringBuilder清空
cc.delete(0, cc.length());
//去掉StringBuilder的最后一个字符
cc.deleteCharAt(cc.length()-1);

思路二:

先去除多余的空格(双指针去除前后的空格,然后遍历赋值给StringBuilder,赋值条件是当前char不为空,或者sb最后不为空

翻转字符串

翻转指定区间内的字符串(双指针)

//StringBuilder
StringBuilder ss = new StringBuilder();
//获取长度和String方法一样
int l = ss.length();
//获取元素也和String一样
char a = ss.charAt(i);
//给指定位置赋值
ss.setCharAt(i, char a);
class Solution {
    public String reverseWords(String s) {
        StringBuilder movedS = removeSpace(s);
        int left = 0;
        int right = 0;
        movedS = reverse(movedS, 0, movedS.length()-1);
        while(right<movedS.length()){
            if(movedS.charAt(right)!= ' '){
                right++;
            }
            else{
                movedS = reverse(movedS, left, right-1);
                left = right;
                left++;
                right++;
            }

        }
        movedS = reverse(movedS, left, right-1);
        return movedS.toString();
    }
    public StringBuilder reverse(StringBuilder s, int start, int end){
        //翻转指定区间内的单词
        while(start<end){
            char temp = s.charAt(start);
            s.setCharAt(start, s.charAt(end));
            s.setCharAt(end, temp);
            start++;
            end--;
        }
        return s;
    }
    public StringBuilder removeSpace(String s){
        //先去除空格--用双指针
        StringBuilder ss = new StringBuilder();
        int left = 0;
        int right = s.length()-1;
        while(s.charAt(left)==' '){
            left++;
        }
        while(s.charAt(right)==' '){
            right--;
        }
        for(int i=left; i<=right; i++){
            char c = s.charAt(i);
            if(c!= ' ' || ss.charAt(ss.length()-1) != ' ' ){
                ss.append(s.charAt(i));
            }
            
        }
        return ss;
    }
}

验证回文字符串

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

思路:首先写一个函数,双指针实现判断是否是回文字符串。主函数是两个指针,前后两个,判断指针的值是否相等,如果不相等就判断左指针+1和右指针-1的子串是不是回文串

class Solution {
    public boolean validPalindrome(String s) {
        int left = 0;
        int right = s.length()-1;
        while(left<right){
            if(s.charAt(left)!=s.charAt(right)){
                return isPalindrome(s, left+1,right)||isPalindrome(s, left, right-1);
            }
            left++;
            right--;
        }
        return true;
    }
    public boolean isPalindrome(String s, int left, int right){
        while(left<right){
            if(s.charAt(left)!=s.charAt(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

迭代

反转列表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

思路:迭代,在遍历链表时,将当前节点的next* 指针改为指向前一个节点

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        while(cur != null){
            ListNode curNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = curNext;
        }
        return pre;
    }
}

两两交换链表中的节点

每两个就交换一下节点

输入:head = [1,2,3,4]
输出:[2,1,4,3]

思路一:迭代,通过指针实现,先定义一个res = new LinkedNode(0),res.next = head,cur指针指向res,然后定义两个指针,cur.next和cur.next.next,然后交换

思路二:递归,递归感觉自己有点绕不出来。递归的结束条件head为空或head.next为空。定义两个指针start和end,修改end指向start,start.next调用递归(传参为原end.next),递归函数返回end。

class Solution {
    public ListNode swapPairs(ListNode head) {
        //递归实现
        if(head ==null || head.next == null){
            return head;
        }
        ListNode pre = head;
        ListNode last = head.next;
        pre.next = swapPairs(last.next);
        last.next = pre;
        return last;
    }
}

搜索-BFS

广度优先搜索可以求解最短路径等最优解问题:第一次吃能顺利到目的节点,其所经过的路径为最短路径。使用BFS只能求解无权图的最短路径,用程序实现BFS时需要考虑以下问题:

队列:用来存储每一轮遍历得到的节点

标记:对于遍历过的节点,应该将它标记,防止重复遍历

二进制矩阵中的最短路径

给你一个 n x n的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1。

public int shortestPathBinaryMatrix(int[][] grid) {
        if(grid.length==0||grid[0].length==0||grid==null){return -1;}
        int n = grid.length;
        int m = grid[0].length;
        Deque<int[]> res = new LinkedList<>();
        int[][] d = {{1, -1}, {1, 0}, {1, 1}, {0, -1}, {0, 1}, {-1, -1}, {-1, 0}, {-1, 1}};
        res.add(new int[]{0,0});//注意这里用add,用push是添加到前面了
        int resLength = 0;
        while(!res.isEmpty()){
            //记录当前层的个数
            int curSize = res.size();
            resLength++;
            for(int i=0; i<curSize; i++){
                int[] curpos = res.poll();
                int curposX = curpos[0];
                int curposY = curpos[1];
                if(grid[curposX][curposY]==1){
                    continue;
                }
                if(curposX==n-1 && curposY==m-1){
                    return resLength;
                }
                grid[curposX][curposY] = 1;//这里很重要,要标记遍历过的点
                for(int j=0; j<d.length; j++){
                    int curX = curposX + d[j][0];
                    int curY = curposY + d[j][1];
                    if(curX>n-1 || curY>m-1 ||curX<0 || curY<0){
                        continue;
                    }
                    res.add(new int[] {curX, curY});
                }
            }
        }
        return -1;
    }
//很重要的一点:要用res.add才可以,因为原本用的res.push,发现add是把元素在队尾添加,而push是在队头添加

搜索-DFS

深度优先搜索是在得到一个新节点立即对新节点进行遍历,直到没有新节点。使用DFS对一个图进行遍历时,常用来求解可达性问题。在实现DFS是需要考虑的问题:

栈:用栈来保存当前节点信息,当遍历新节点返回时能继续遍历当前节点,可以使用递归栈

标记:和BFS一样需要对已经遍历过的节点进行标记

岛屿的最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

思路:想知道每个网格中每个连通形状的面积,然后取最大值;在一个土地上,以四个方向探索与之相连的每一个土地,如果土地为0就返回;保证每个土地只遍历一次;

public int maxAreaOfIsland(int[][] grid) {
        int res = 0;
        for(int i=0; i<grid.length; i++){
            for(int j=0; j<grid[0].length; j++){
                res = Math.max(res, dfs(grid, i, j));
            }
        }
        return res;
    }
    public int dfs(int[][] grid, int ii, int jj){
        if(ii<0||jj<0||ii>grid.length-1||jj>grid[0].length-1||grid[ii][jj]==0){
            return 0;
        }
        grid[ii][jj] = 0;//这里很重要
        int curRes = 1;
        int[][] direction = new int[][]{{0,1}, {0,-1}, {1,0}, {-1,0}};
        for(int curi=0; curi<direction.length; curi++){
            curRes += dfs(grid, ii+direction[curi][0], jj+direction[curi][1]);
        }
        return curRes;
    }

岛屿数量

和上面那个题目很相似,差别在于这题是统计岛屿的数量。

public int numIslands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }
        int res = 0;
        for(int i =0; i<grid.length; i++){
            for(int j=0; j<grid[0].length; j++){
                if(grid[i][j]!='0'){
                    dfs(grid, i, j);
                    res++;
                }   
            }
        }
        return res; 
    }
    public void dfs(char[][] grid, int ii, int jj){
        if(ii<0||jj<0||ii>grid.length-1||jj>grid[0].length-1||grid[ii][jj]=='0'){
            return;
        }
        grid[ii][jj] = '0';
        int[][] direction = new int[][]{{0,1}, {0,-1}, {1,0}, {-1, 0}};
        for(int i=0; i<direction.length; i++){
            dfs(grid, ii+direction[i][0], jj+direction[i][1]);
        }
    }

回溯

回溯算法是用来解决--遍历找到所有的可行解

框架:

List<List<Integer>> res = new ArrayList<>();
Deque<Integer> track = new ArrayDeque<>();
backtracking(路径,选择track);
return res;
public void backtracking(路径,选择track){
	//递归结束
	if(找到了可行解){
		res.add(new ArrayList(track));
		return;
	}
	for(选择:选择列表){
		//做选择
		track.addLast(选择);
		backtracking(路径,track);
		//撤销选择
		track.removeLast();
	}
}

复原IP地址

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。

思路:利用回溯算法,但是可以考虑剪枝操作

一开始判断,长度小于4或大于12,直接返回;如果切分出来的地址段不符合格式要求,退出循环;

//字符串的拼接
Deque<String> path = new ArrayDeque<>();
String s = String.join(".", path);
//10的i次方
Math.pow(10, i);
class Solution {
    public List<String> restoreIpAddresses(String s) {
        //首先定义path、res
        List<String> res = new ArrayList<>();
        Deque<String> path = new ArrayDeque<>();
        int slen = s.length();
        if(slen<4||slen>12){
            return res;
        }
        backtracking(s, 0, 0, res, path);
        return res; 
    }
    public int isLegal(String ip, int start, int end){
        //判断是否合法
        String s = ip.substring(start, end+1);
        if(s.length()>1){
            if(s.charAt(0)=='0'){
                return -1;
            }
        }
        //转成int格式
        int res = 0;
        for(int i=s.length()-1; i>=0; i--){
            res += (s.charAt(i)-'0')*Math.pow(10,s.length()-1-i);
        }
        if(res > 255){
            return -1;
        }
        return res;
    }
    public void backtracking(String s,int begin, int deep, List<String> res, Deque<String> path){
        if(deep==4 && begin == s.length()){
            res.add(String.join(".", path));
            return;
        }
        //如果不够分了
        if((s.length()-begin)<(4-deep)||(s.length()-begin)>3*(4-deep)){
            return;
        }
        for(int i=0; i<3; i++){
           if(begin+i>=s.length()){
               break;
           }
            int current = isLegal(s, begin, begin+i);
            if(current != -1){
                path.addLast(current + "");
                backtracking(s, begin+i+1, deep+1, res, path);
                path.removeLast();
            }
        } 
    }
}

电话号码的字母组合

给定一个仅包含数字2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。

思路:

要求所有组合,说明要用回溯方法,然后res就是要求的结果,遍历数字串,可选择的路径当前数字对应的字母组合。找到可行解的条件是遍历到最后一个数字。

public List<String> letterCombinations(String digits) {
        if(digits.length()==0){return new ArrayList<>();}
        //建一个字典
        HashMap<Character, String> mp = new HashMap<>();
        mp.put('2', "abc");
        mp.put('3', "def");
        mp.put('4', "ghi");
        mp.put('5', "jkl");
        mp.put('6', "mno");
        mp.put('7', "pqrs");
        mp.put('8', "tuv");
        mp.put('9', "wxyz");
        ArrayList<String> res = new ArrayList<>();
        backtracking(res, mp, digits, 0,  new StringBuilder());
        return res;
    }
    public void backtracking(ArrayList<String> res, HashMap<Character,String> mp, String digits, int index, StringBuilder ss){
        if(index==digits.length()){
            res.add(ss.toString());
            return;
        }
        char aa = digits.charAt(index);
        String curString = mp.get(aa);
        for(int i=0; i<curString.length(); i++){
            ss.append(curString.charAt(i));
            backtracking(res, mp, digits, index+1, ss);
            ss.deleteCharAt(ss.length()-1);
        }
    }

单词搜索

给定一个 m x n二维字符网格board和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

思路:遍历每一个board的字符,函数判断以该字符为起点能不能找到word,找word的函数用回溯做。

回溯结束的条件是找到的长度==word的长度

需要有一个visit数组记录已经访问过的位置

public static boolean exist(char[][] board, String word) {
        int h = board.length, w=board[0].length;
        int[][] visited = new int[h][w];
        for(int i=0; i<h; i++){
            for(int j=0; j<w; j++){
                boolean flag = isMatch(board, visited, i, j, 0, word);
                if(flag){
                    return true;
                }
            }
        }
        return false;
    }
    public static boolean isMatch(char[][] board, int[][] visited, int i, int j, int k, String word){
        if(word.charAt(k)!=board[i][j]){return false;}
        else if(k==word.length()-1){return true;}
        visited[i][j] = 1;
        int[][] direction = new int[][] {{1,0}, {-1,0}, {0,1}, {0,-1}};
        boolean res = false;
        for(int index=0; index<4; index++){
            int curI = i+direction[index][0];
            int curJ = j+direction[index][1];
            if(curI>=0&&curI<board.length&&curJ>=0&&curJ<board[0].length){
                if(visited[curI][curJ]==0){
                    boolean flag = isMatch(board, visited, curI, curJ, k+1, word);
                    if(flag){
                        res = true;
                        break;
                    }
                }
            }
        }
        visited[i][j] = 0;//这里很重要
        return res;
    }

数组的全排列

给定一个不含重复数字的数组nums,返回其所有可能的全排列

思路:用回溯做,需要定义一个标识位flag,标记当前节点被访问过

 public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        boolean[] flag = new boolean[len]; 
        ArrayList<Integer> paths = new ArrayList<>();
        backtracking(res, nums, paths, 0, len, flag);
        return res;
    
    }
    public void backtracking(List<List<Integer>> res,int[] nums, List<Integer> paths, int index, int len, boolean[] flag){
        if(index == len){
            res.add(new ArrayList(paths));
        }
        for(int i=0; i<len; i++){
            if(!flag[i]){
                flag[i]=true;
                paths.add(nums[i]);
                backtracking(res, nums, paths, index+1, len, flag);
                flag[i] = false;
                paths.remove(paths.size()-1);
            
            }
        }

字符串的全排列

class Solution {
    List<String> res = new LinkedList<>();
    char[] c;
    public String[] permutation(String s) {
        c = s.toCharArray();
        dfs(0);
        return res.toArray(new String[res.size()]);
    }
    void dfs(int x) {
        if(x == c.length - 1) {
            res.add(String.valueOf(c));      // 添加排列方案
            return;
        }
        HashSet<Character> set = new HashSet<>();
        for(int i = x; i < c.length; i++) {
            if(set.contains(c[i])) continue; // 重复,因此剪枝
            set.add(c[i]);
            swap(i, x);                      // 交换,将 c[i] 固定在第 x 位
            dfs(x + 1);                      // 开启固定第 x + 1 位字符
            swap(i, x);                      // 恢复交换
        }
    }
    void swap(int a, int b) {
        char tmp = c[a];
        c[a] = c[b];
        c[b] = tmp;
    }
}

排序

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

思路:先对数组按照每个子元素的第一位进行排序,然后从前往后边数组,先把第一个加入结果res中,然后之后的每一个子元素和res的最后一个比较,看是否能重叠,重叠就更新res的最后一个元素,不重叠就添加进res里

//res
List<int[]> res = new ArrayList<int[]>();
//对二维int数组排序
Arrays.sort(intervals, (l1, l2)->l1[0]-l2[0]);
//res转成int[][]型返回
res.toArray(new int[res.size()][]);
class Solution {
    public int[][] merge(int[][] intervals) {
        if(intervals.length==0){return new int[][];}
        //先排序
        Arrays.sort(intervals, (v1, v2)->v1[0]-v2[0]);
        List<int[]> res = new ArrayList<int[]>();
        res.add(intervals[0]);
        for(int i=1; i<intervals.length; i++){
            int[] current = intervals[i];
            int[] last = res.get(res.size()-1);
            if(current[0]>= last[0] && current[0]<=last[1]){
                if(current[1]>=last[1]){
                    res.set(res.size()-1, new int[] {last[0], current[1]});
                }
                else{
                    res.set(res.size()-1, new int[] {last[0], last[1]});
                }
            }
            else{
                res.add(current);
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

排序算法

    public static int[] bubbleSort(int[] a){
        //冒泡排序
        int length = a.length;
        for(int i=0; i<length-1; i++){
            for(int j=0; j<length-i-1; j++){
                //比较两个交换
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
        return a;
    }

    public static int[] selectSort(int[] a){
        //选择排序
        int index = 0;
        while(index<a.length-1){
            //选择未排序里面最小的
            int currentMin = a[index];
            int minIndex = index;
            for(int j=index+1; j<a.length; j++){
                if(a[minIndex]>a[j]){
                    minIndex = j;
                }
            }
            //交换index和minIndex的值
            int temp = a[index];
            a[index] = a[minIndex];
            a[minIndex] = temp;
            index++;
        }
        return a;
    }

    public static int[] insertSort(int[] a){
        //插入排序
        for(int index = 1; index<a.length; index++){
            int pre = index-1;
            int cur = a[index];
            while(pre>=0 && a[pre]>cur){
                a[pre+1] = a[pre];
                pre--;
            }
            a[pre+1] = cur;
        }
        return a;
    }

    public static int[] mergeSort(int[] a){
        //归并排序
        if(a.length==1){
            return a;
        }
        int mid = a.length/2;
        int[] aLeft = Arrays.copyOfRange(a, 0, mid);
        int[] aReft = Arrays.copyOfRange(a, mid, a.length);
        return merge(mergeSort(aLeft), mergeSort(aReft));


    }
    public static int[] merge(int[] a1, int[] a2){
        //合并两个排好序的数组
        int[] res = new int[a1.length+a2.length];
        int a1Left = 0;
        int a2Left = 0;
        int k = 0;
        while(a1Left != a1.length && a2Left != a2.length){
            if(a1[a1Left] < a2[a2Left]){
                res[k] = a1[a1Left];
                k++;
                a1Left++;
            }
            else{
                res[k] = a2[a2Left];
                k++;
                a2Left++;
            }
        }
        if(a1Left!= a1.length){
            while (a1Left<a1.length){
                res[k] = a1[a1Left];
                k++;
                a1Left++;
            }
        }
        if(a2Left!= a2.length){
            while (a2Left<a2.length){
                res[k] = a2[a2Left];
                k++;
                a2Left++;
            }
        }
        return res;
    }


    public static void quickSort(int[] a, int l, int r){
        //快速排序
        if(l<r){
            int standard = a[l];
            int i = l+1;
            int j = l+1;
            while (j<=r){
                if(a[j]<standard){
                    int stemp = a[j];
                    a[j] = a[i];
                    a[i] = stemp;
                    i++;
                    j++;
                }
                else{
                    j++;
                }
            }
            int temp = a[i-1];
            a[i-1] = standard;
            a[l] = temp;
            quickSort(a, l, i-2);
            quickSort(a, i, r);
        }
    }
    public static int[] aa(int[] n){
        quickSort(n, 0, n.length-1);
        return n;
    }

贪心

每次选取局部最优,然后实现全局最优

分发饼干

求能满足最多数量的孩子,g表示孩子,s表示饼干

输入: g = [1,2,3], s = [1,1]
输出: 1
输入: g = [1,2], s = [1,2,3]
输出: 2

思路:要想孩子的数量尽量多,就要先满足胃口小的孩子。先对两个数组排序,然后每次把最小能满足孩子的饼干,分给孩子

public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int res = 0;
        int gIndex = 0;
        int sIndex = 0;
        int gLen = g.length;
        int sLen = s.length;
        while(gIndex<gLen && sIndex<sLen){
            if(s[sIndex]>=g[gIndex]){
                res++;
                sIndex++;
                gIndex++;
            }
            else{
                sIndex++;
            }
        }
        return res;
    }
    //还可以优化一下,其实不用定义res,结果直接返回gIndex即可

无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

思路:先对数组排序,按照右边界限排序。每次选择区间结尾最小的,留给后面的空间就越大。然后从左遍历列表,记录不重叠的区间个数,怎么判断是不重叠的呢?当下一个的左边界>=上一个的右边界,就代表不重叠

public int eraseOverlapIntervals(int[][] intervals) {
        //先对数组进行排序
        Arrays.sort(intervals, (v1,v2)->v1[1]-v2[1]);
        int res = 1;
        int right = intervals[0][1];
        int len = intervals.length;
        for(int i=1; i<len; i++){
            int curLeft = intervals[i][0];
            int curRight = intervals[i][1];
            if(curLeft>=right){
                right = curRight;
                res++;
            }
        }
        return len-res;
    }

用最少数量的箭引爆气球

也是求无重叠子区间的个数,但是不同之处在于,二维数组有正有负

要按照右边界从小到大排序
// Arrays.sort(points, (v1, v2)->v1[1]-v2[1]);//这个排序会出错,负数排在了正数后面
//可以这样写
Arrays.sort(points, (o1, o2) -> Integer.compare(o1[1], o2[1]));

//也可以这样写
Arrays.sort(points,new Comparator<int[]>(){
    public int compare(int[] a,int[] b){
        if(a[1]>b[1]){
            return 1;
        }
        else{return -1;}
    }
});
//对于怎么重写比较器
compare的返回值:1表示两个比较的数字位置互换,0和-1表示位置不发生变化

Integer.compare(a, b))
上面的a和b是要比较的两个整数值。如果val1<val2,返回值为-1。当val1 == val2时,返回值为1。当val1>val2时,返回值为1。


//二维数组排序,按第一个元素降序,第二个元素升序
Arrays.sort(points, new Comparator<int[]>(){
    public int compare(int[] a, int[] b){
        if(a[0]==b[0]){
            return a[1]-b[1];
        }
        else{
            return b[0]-a[0];
        }
    }
});

根据身高重建队列

思路:需要先对数组进行排序,然后按照第二个元素一次插入队列中

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //先对数组排序
        Arrays.sort(people, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                if(a[0]==b[0]){
                    return a[1] - b[1];
                }
                else{
                    return b[0]-a[0];
                }
            }
        });
        //遍历people,按照第二个数进行插入
        List<int[]> res = new ArrayList<>();
        for(int[] p:people){
            res.add(p[1], p);
        }
        return res.toArray(new int[res.size()][]);
    }
}

二分查找

动态规划

动态规划问题的一般问题就是求最值,要求最值就需要穷举所有可行解,然后在可行解里求最值。

动态规划三要素:

​ 重叠子问题:如果暴力穷举会效率极低,所以需要备忘录或PD table来优化穷举过程

​ 最优子结构:通过子问题的最值得到原问题的最值,子问题之间必须相互独立

​ 状态转移方程:穷举所有可行解

怎么确定状态转移方程:base case->明确[状态]->明确[选择]->定义dp数组/函数的定义

# 初始化base case
dp[0][0][...] = base
#进行状态转移
for 状态1 in 状态1的所有取值:
	for 状态2 in 状态2的所有取值:
		for...
			dp[状态1][状态2][...] = 求最值(选择1,选择2...)

凑零钱问题

自底向上,生成dp数组,状态转移方程如下:

image-20210814161816558

cj为第j个硬币的币值

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列的长度。如果不存在公共子序列 ,返回0

状态定义:dp[i] [j]表示text1[0: i-1 ]和text2[0: j-1]的最长公共子序列,为什么不是[0-i],是为了方便当i=0时,dp[i] [j]表示空字符和灵位一个字符串匹配

状态转移方程

​ dp[i] [j] = dp[i-1] [j-1]+1 if test[i-1]==text[j-1]

​ dp[i] [j] = max(dp[i] [j-1], dp[i] [j-1] ) if test[i-1]!=text[j-1]

状态的初始化:dp[i] [j] = 0 if i=0 or j=0

遍历方向:因为是求test1和text2的最长公共子序列,所以是从0到i=len(text1), j=len(text2)

public int longestCommonSubsequence(String text1, String text2) {
        int[][] dp = new int[text1.length()+1][text2.length()+1];
        for(int i=1; i<=text1.length(); i++){
            char t1 = text1.charAt(i-1);
            for(int j=1; j<=text2.length(); j++){
                char t2 = text2.charAt(j-1);
                if(t1==t2){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                else{
                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[text1.length()][text2.length()];
    }

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

思路:动态规划,爬n阶的方法=爬n-1阶的方法+爬n-2阶的方法

连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

创建一个bp数组,每个bp[i]表示nums[..i]的连续子数组的和

public int maxSubArray(int[] nums) {
        int res = nums[0];
        for(int i = 1; i < nums.length; i++) {
            nums[i] += Math.max(nums[i - 1], 0);
            res = Math.max(res, nums[i]);
        }
        return res;
    }

完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

思路:用动态规划做,初始状态f(0) = 0转移方程为:

image-20210825111308881

public int numSquares(int n) {
        int[] dp = new int[n+1];
        for(int i=1; i<=n; i++){
            dp[i] = Integer.MAX_VALUE;
            for(int j=1; j<=Math.sqrt(i); j++)
            {
                dp[i] = Math.min(dp[i-j*j]+1, dp[i]);
            }
        }
        return dp[n];
    }

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

思路:

定义子问题:子问题就是从前k个房间内偷出的最大金额,而且注意子问题一定要是满足k=n的情况
递推关系:f(k) = max(f(k-1), f(k-2)+H(k-1) 最大金额就是,前k-1个房间能偷的最大金额和前k-2个房间能偷的最大金额+当前第k个房间的金额f(0) = 0,f(1) = H(k-1)

最长回文子串

给你一个字符串s,找出s中最长的回文子串

思路:用一个dp数组保存,dp[i] [j]表示s[i-j]是不是回文串,如果dp[i+1] [j-1]是回文串且s[i]==s[j],那么dp[i] [j]也是回文串

public String longestPalindrome(String s) {
        int len = s.length();
        if(len==1){return s;}
        boolean[][] dp = new boolean[len][len];
        for(int i=0; i<len; i++){
            dp[i][i] = true;
        }
        //找所有子串
        int maxLen = 0;
        String result = new String();
        for(int curLen=1;curLen<=len; curLen++){
            for(int start = 0; start<len; start++){
                int end = start+curLen-1;
                if(end<len){
                    if((curLen==1||curLen==2) && s.charAt(start)==s.charAt(end)){dp[start][end]=true;}
                    else if(dp[start+1][end-1] && s.charAt(start)==s.charAt(end)){dp[start][end]=true;}
                    if(dp[start][end] && end-start+1 > maxLen){
                        result = s.substring(start, end+1);
                    }
                }
            }
        }
        return result;
    }

数据结构相关

链表

旋转链表

给你一个链表的头节点head,旋转链表,将链表每个节点向右移动 k个位置。

思路:先把链表串成环,同时统计链表长度length, 在根据k和length找出链表应该结束的位置,然后指向null,返回结束位置的后一个节点即可

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(k==0||head==null||head.next==null){
            return head;
        }
        //先看一下链表的长度,并串成环
        ListNode first = head;
        ListNode cur = head;
        int length = 1;
        while(cur.next!=null){
            cur = cur.next;
            length++;
        }
        if(k%length==0){return head;}
        cur.next = first;
        for(int i=0; i<length-1-k%length; i++){
            head = head.next;
        }
        ListNode headNext = head.next;
        head.next = null;
        return headNext;
    }
}

相交链表

判断两个链表是否相交,相交就返回相交的那个节点

思路一:遍历a链表,把每个节点存在一个list里面;遍历b链表,把每个节点存在另一个list里面。然后从后往前判断两个链表的节点是否相同,第一个不相同的节点那里跳出循环

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode indexA = headA;
        ListNode indexB = headB;
        ArrayList<ListNode> lisA = new ArrayList<>();
        ArrayList<ListNode> lisB = new ArrayList<>();
        while(indexA != null){
            lisA.add(indexA);
            indexA = indexA.next;
        }
        while(indexB != null){
            lisB.add(indexB);
            indexB = indexB.next;
        }
        int aLen = lisA.size();
        int bLen = lisB.size();
        int minLen = Math.min(aLen, bLen);
        ListNode res = new ListNode();
        if(lisA.get(aLen-1)==lisB.get(bLen-1)){
            res = lisA.get(aLen-1);
        }
        else{
            return null;
        }
        for(int i=0; i<minLen; i++){
            if(lisA.get(aLen-i-1)==lisB.get(bLen-i-1)){
                res = lisA.get(aLen-i-1);
            }
            else{
                break;
            }
        }
        return res;
    }

思路二:定义两个指针,一个指向a,一个指向b,判断指针指向的节点是否相同并指针向后移,当a指向null时,再接上b,b指向null再接上a,原理是如果相交,两个指针走过的路径是相同的,如果不想交,两个指针最后都指向null

奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

思路:定义四个指针,分别是奇数节点的头指针、尾指针,偶数节点的头指针、尾指针。然后定义一个p=head.next.next,遍历链表

public ListNode oddEvenList(ListNode head) {
        if(head==null || head.next==null){return head;}
        ListNode oddHead = head;
        ListNode oddTail = oddHead;
        ListNode evenHead = head.next;
        ListNode evenTail = evenHead;
        ListNode p = head.next.next;
        while(p != null){
            oddTail.next = p;
            oddTail = oddTail.next;
            p = p.next;
            if(p!=null){
                evenTail.next = p;
                evenTail = evenTail.next;
                p = p.next;
            }
        }
        oddTail.next = evenHead;
        evenTail.next = null;
        return oddHead;
    }

求二叉树的最大深度

用深度优先搜索

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

平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

思路:自底向上判断以当前节点为根节点的树是不是平衡二叉树,因为自顶向下会重复计算,时间复杂度会高。自底向上求当前节点的高度,如果不是

class Solution {
    public boolean isBalanced(TreeNode root) {
        return height(root)>=0;
    }
    public int height(TreeNode root){
        if(root == null){return 0;}
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        if(leftHeight==-1||rightHeight==-1||Math.abs(leftHeight-rightHeight)>1){
            return -1;
        }
        else{
            return Math.max(leftHeight, rightHeight)+1;
        }
    }
}

二叉树的直径

一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

思路:深度优先搜索,每个节点对应的最大长度是左子树的高度+右子树的高度,所以写一个统计树高度的函数 ,然后一个全局int res,每次计算节点的最大长度,然后更新res

class Solution{
	int res;
	public int diameterOfBinaryTree(TreeNode root){
		res = 0;
		int l = treeHeight(root);
		return res;
	}
	public int treeHeight(TreeNode root){
		if(root == null){return 0;}
		int leftHeight = treeHeight(root.left);
		int rightHeight = treeHeight(root.right);
		res = Math.max(res, leftHeight+rightHeight);
		return Math.max(leftHeight, rightHeight)+1;
	}
}

翻转二叉树

把一个树每个节点的左右子树交换

思路:递归的思想,当前节点的左子树和右子树交换

public TreeNode invertTree(TreeNode root) {
        if(root == null){return root;}
        TreeNode temp = invertTree(root.left);
        root.left = invertTree(root.right);
        root.right = temp;
        return root;
    }

合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

思路:如果root1为空,就直接返回root2,如果root2为空,就返回root1。否则新建节点=root1.val+root2.val,

新节点的left是递归root1.left和root2.left的结果,新节点的right是递归root1.right和root2.right的结果

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1 == null){
            return root2;
        }
        if(root2 == null){
            return root1;
        }
        TreeNode merg = new TreeNode(root1.val+root2.val);
        merg.right = mergeTrees(root1.right, root2.right);
        merg.left = mergeTrees(root1.left, root2.left);
        return merg;
    }

二叉树的所有路径

结果需要返回从根节点到叶子节点的所有路径

思路:深度遍历实现

public List<String> binaryTreePaths(TreeNode root) {
        ArrayList<String> ar = new ArrayList<>();
        current(root, "", ar);
        return ar;
    }
    public void current(TreeNode root, String s, ArrayList<String> paths){
        if(root != null){
            StringBuffer ss = new StringBuffer(s);
            ss.append(Integer.toString(root.val));
            if(root.left==null && root.right==null){
                paths.add(ss.toString());
            }
            else{
                ss.append("->");
                current(root.left, ss.toString(), paths);
                current(root.right, ss.toString(), paths);
            }
        }
    }

栈和队列

最小栈

设计一个支持 push,pop,top操作,并能在常数时间内检索到最小元素的栈

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]

思路:借助另外一个栈,和创建的栈元素一一对应,用来保存每个元素当前对应的栈的最小值。当一个元素要入栈时,取当前辅助栈的栈顶元素和当前元素的最小值,把最小值入辅助栈;当一个元素要出栈时,辅助栈也弹出;获取最小值就是辅助栈的栈顶元素

有效的括号

成对的括号

用字典来保存成对的括号,然后便利字符串,用一个队列保存,最后判断队列是否为空

public boolean isValid(String s) {
        HashMap<Character, Character> mp = new HashMap<>();
        mp.put(')', '(');
        mp.put(']', '[');
        mp.put('}', '{');
        Deque<Character> sQueue = new LinkedList<>();
        for(int i=0; i<s.length(); i++){
            Character curChar = s.charAt(i);
            if(mp.containsKey(curChar)){
                if(sQueue.peek()==mp.get(curChar)){
                    sQueue.poll();
                }
                else{
                    return false;
                }
            }
            else{
                sQueue.push(curChar);
            }
        }
        if(sQueue.isEmpty()){
            return true;
        }
        else{
            return false;
        }
    }

两个栈实现一个队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

class CQueue {
    Stack<Integer> stack_1;
    Stack<Integer> stack_2;
    public CQueue() {
        stack_1 = new Stack<Integer>();
        stack_2 = new Stack<Integer>();
    }
    public void appendTail(int value) {
        stack_1.push(value);
    }
    public int deleteHead() {
        if (stack_2.isEmpty()){
            while(!stack_1.isEmpty()){
                stack_2.push(stack_1.pop());
            }
        }
        if(stack_2.isEmpty()){
            return -1;
        }
        else{
            int delitem = stack_2.pop();
            return delitem;
        }
    }
}

两个队列实现一个栈

思路:队列1的出队顺序就是栈的出栈顺序,每次入栈,就是先把数据从que1转到que2,然后que1添加x,再把que2中的数据移到que1

class MyStack {
    Deque<Integer> que1;
    Deque<Integer> que2;
    public MyStack() {
        que1 = new LinkedList<>();
        que2 = new LinkedList<>();
    }
    public void push(int x) {
        //先把数据从que1转到que2,然后que1添加x,再把que2中的数据移到que1
        while(!que1.isEmpty()){
            que2.offer(que1.pop());
        }
        que1.offer(x);
        while(!que2.isEmpty()){
            que1.offer(que2.pop());
        }
    }
    public int pop() {
        return que1.pop();
    }
    public int top() {
        return que1.peek();
    }
    public boolean empty() {
        return que1.isEmpty();
    }
}

哈希表

字符串

数组和矩阵

非递减数组

判断数组,在最多改变一个元素的情况下,该数组能否变成一个非递减数组

思路:定义pre和next,如果pre>next,判断pre的前一个是不是大于next,如果大于的话,把next改成pre的前一个值,并记录更改的次数,不能超过两次

public boolean checkPossibility(int[] nums) {
        int numsLen = nums.length;
        if(numsLen<=1){return true;}
        int flag = 0;
        for( int i=0; i<numsLen-1; i++){
            int pre = nums[i];
            int next = nums[i+1];
            if(pre>next){
                flag++;
                if(flag>1){return false;}
                if(i>0 && next<nums[i-1]){
                    nums[i+1] = pre;
                }
            }
        }
        return true;
    }

位运算

posted @ 2021-08-16 21:40  阿甘吖  阅读(53)  评论(0)    收藏  举报