#算法基础知识与刷题笔记----双指针;二分查找;滑动窗口;回溯法;BFS;DFS;分治法;递归;拓扑排序;Tries;Union Find;动态规划;

由于上一篇写太多造成页面卡顿而不得不重开一篇orz

一、双指针

java中不能像C一样对指针直接操作,但是我们定义变量的过程就是定义指针的过程。

注意:

1、指针变量有两层含义

	  1) 指针变量里存的是地址(它指向的变量的首地址)。
	
	  2) 指针变量有类型,类型说明了该指针指向的变量在内存中的范围(大小)。

2、使用创建一个对象包括声明和定义。

		1) 声明是指定义一个指向该对象的指针变量。
		
		2) 定义是指用new关键字在堆内存中创建了对象,并把他的首地址付给那个指针变量。

二、LeetCode344

反转字符串

class Solution {
    public void reverseString(char[] s) {
        int j = s.length - 1;
        char temp = 0;
        for(int i = 0; i < s.length; i++){
            if(i < j){
                temp = s[i];
                s[i] = s[j];
                s[j--] = temp;
            }
        }
    }
}

这里的双指针是i和j吧

三、LeetCode27

移出元素

class Solution {
    public int removeElement(int[] nums, int val) {
        int length = 0;
        for(int i = 0; i < nums.length; ++i){
            if(nums[i] != val){
                nums[length++] = nums[i];
            }  
        }
        return length;
    }
}

所以双指针更重要的是思想吧,比官方答案还少了一行诶!

四、LeetCode125

验证回文串
"0P"这个测试样例真是绝了,0和P相差也是32;

class Solution {
    public boolean isPalindrome(String s) {
        if(s.length() == 0)   return true;
        int j = s.length()-1;
        while(is(s.charAt(j)) != true){
            if(--j <= 0)   return true;
        } 
        for(int i = 0; i <= j; ++i, --j){
            while(is(s.charAt(i)) != true) i++;
            while(is(s.charAt(j)) != true) j--;
            if(s.charAt(i) == s.charAt(j))  continue;
            else if(Math.abs(s.charAt(i) - s.charAt(j)) == 32 && s.charAt(i) > '9' && s.charAt(j) >'9') continue;
            else return false;
        }
        return true;
    }
    public boolean is(char A){
        return (A <= 'Z' && A >= 'A')||(A <= 'z' && A >= 'a')||(A <= '9' && A >= '0');
    }
}

java

判断字符是否为数字

Character.isDigit(char ch);

判断字符是否为字母

Character.isLetter(char ch)

五、LeetCode287

寻找重复数
企图用寻找缺失元素的异或方法,然而这里可能一个元素重复多次,所以这么写不能通过。

class Solution {
    public int findDuplicate(int[] nums) {
        int temp = 0;
        for(int i = 0; i < nums.length - 1; i++){
            temp ^= (i+1) ^ nums[i];
        }
        return temp ^ nums[nums.length - 1];
    }
}

1.然后用哈希表做一遍

class Solution {
    public int findDuplicate(int[] nums) {
        Map<Integer, Boolean> hash = new HashMap<>();
        for(int num:nums){
            if(hash.containsKey(num))   return num;
            else hash.put(num, true);
        }
        return nums[0];
    }
}

2.直接排序查找的话好像也差不多。

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

3.再快慢指针:

class Solution {
    public int findDuplicate(int[] nums) {
        //快慢指针
        int slow = 0;
        int fast = 0;
        do{
            slow = nums[slow];
            fast = nums[nums[fast]];
        }while(slow != fast);
        int finder = 0;
        while(finder != slow){
            slow = nums[slow];
            finder = nums[finder];
        }
        return finder;
    }
}

效率提了上来,相遇的地方就是重复数字。
4.二分法,时间换空间:

class Solution {
    public int findDuplicate(int[] nums) {
        //二分
        int left = 1;
        int right = nums.length-1;
        while(left < right){
            int mid = (left + right) >> 1;
            int cnt = 0;
            for(int num:nums){
                if(num <= mid) cnt++;
            }
            if(cnt > mid){
                right = mid;
            }else{
                left = mid + 1;
            }
        }
        return left;    
    }
}

这题信息量有点大,估计后面还会碰到。
总结一下某解析说的是这道题用二分和快慢指针的方法比较特殊难想到,而且尤其是二分法用时间换空间的做法在实际过程中比较难见到,所以仅当做特殊情况来处理。

六、LeetCode141、142

环形链表
有了上一题的基础,这两题就很快写出来了。


一、二分查找

上一题已经用到了二分查找的思想,其常用于处理数组查找相关的问题,可以将时间复杂度从O(n)降为O(logn)。

二、LeetCode35

搜索插入位置
比如这题,在边界条件上不能只套模板,这题与刚刚那道查找重复元素二分的内容并不同,这里是有序的数组,二分下标,刚刚那个是[1,n],二分数字。
所以对应的条件也有不同

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = (left + right) >> 1;
            if(nums[mid] == target){
                return mid;
            }
            else if(nums[mid] < target){
                left = mid + 1;
            }
            else{
                right = mid - 1;
            }
        }
        return left;
    }
}

三、LeetCode1095

山脉数组中查找目标值
做的第一道困难难度的题诶,居然一下子做出来了

/**
 * // This is MountainArray's API interface.
 * // You should not implement it, or speculate about its implementation
 * interface MountainArray {
 *     public int get(int index) {}
 *     public int length() {}
 * }
 */
 
class Solution {
    public int findInMountainArray(int target, MountainArray mountainArr) {
        //找山峰
        int left = 0;
        int right = mountainArr.length() - 1;
        int mid = (left + right) >> 1;
        int top = mid;
        while(left <= right){
            mid = (left + right) >> 1;
            if(mountainArr.get(mid) < mountainArr.get(mid + 1)){
                left = mid + 1;
            }
            else if(mountainArr.get(mid) < mountainArr.get(mid - 1)){
                right = mid - 1;
            } 
            else{
                top = mid;break;
            }
        }
        //找target     
        //左边
        left = 0;
        right = top;
        while(left <= right){
            mid = (left + right) >> 1;
            if(mountainArr.get(mid) == target){
                return mid;
            }
            else if(mountainArr.get(mid) < target){
                left = mid + 1;
            } 
            else{
                right = mid - 1;
            }
        }
        //右边   
        left = top;
        right = mountainArr.length() - 1;
        while(left <= right){
            mid = (left + right) >> 1;
            if(mountainArr.get(mid) == target){
                return mid;
            }
            else if(mountainArr.get(mid) > target){
                left = mid + 1;
            } 
            else{
                right = mid - 1;
            }
        }
        //找不到
        return -1;
    }
}

四、LeetCode4

寻找两个正序数组的中位数
这里要处理两个数组,我一下子想不到二分法的处理办法,先用其他方法实现一下(好像是双指针)。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if(nums1.length == 0)    return (nums2[nums2.length/2] + nums2[(nums2.length-1)/2])/2.00000;
        if(nums2.length == 0)    return (nums1[nums1.length/2] + nums1[(nums1.length-1)/2])/2.00000;
        int num = nums1.length + nums2.length;
        int num1 = 0;
        int num2 = 0;
        int temp = 0;
        while(temp < (num - 1)/2){
            if(num2 >= nums2.length)    num1++;
            else if(num1 >= nums1.length)   num2++;
            else if(nums1[num1] < nums2[num2])    num1++;
            else   num2++;
            temp++;
        }
        int a = 0;
        if(num2 >= nums2.length)    a = nums1[num1++];
        else if(num1 >= nums1.length)    a = nums2[num2++]; 
        else if(nums1[num1] < nums2[num2])    a = nums1[num1++];
        else   a = nums2[num2++];
        if(num % 2 != 0)    return a/1.00000;
        int b = 0;
        if(num2 >= nums2.length)    b = nums1[num1++];
        else if(num1 >= nums1.length)    b = nums2[num2++]; 
        else if(nums1[num1] < nums2[num2])    b = nums1[num1++];
        else   b = nums2[num2++];
        return (a + b)/2.00000;
    }
}

显得有些臃肿。
不过这题用二分确实代码有点难写,我先看懂以后看能不能手写出来。

五、LeetCode704

这道二分查找题才应该第一个做,

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

六、LeetCode162

寻找峰值
这题也是1095的基础题,求前面的峰值

class Solution {
    public int findPeakElement(int[] nums) {
        //找山峰
        if(nums.length == 1)    return 0;
        if(nums.length == 2)    return nums[0]>nums[1]?0:1;
        int left = 0;
        int right = nums.length - 1;
        int mid = (left + right) >> 1;
        while(left <= right){
            mid = (left + right) >> 1;
            if(mid != nums.length - 1 && nums[mid] < nums[mid + 1]){
                left = mid + 1;
            }
            else if(mid != 0 && nums[mid] < nums[mid - 1]){
                right = mid - 1;
            } 
            else{
                return mid;
            }
        }
        return 0;
    }
}

直接做出来了1095,感觉二分可以了。

七、LeetCode74

搜索二维矩阵
这题还挺有意思的,二维二分,是前面做过两题的结合

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        //搜索目标行
        int left = 0;
        int right = matrix.length - 1;
        int line = 0;
        if(matrix.length > 1){
            while(left <= right){
                int mid = (left + right) >> 1;
                if(matrix[mid][0] == target){
                    return true;
                }
                else if(matrix[mid][0] < target){
                    left = mid + 1;
                }
                else{
                    right = mid - 1;
                }
            }
            line = left;
            if(line >= matrix.length || matrix[line][0] > target)    line--;
            if(line < 0)    return false;
        }
        //搜索目标值
        left = 0;
        right = matrix[0].length - 1;
        while(left <= right){
            int mid = (left + right) >> 1;
            if(matrix[line][mid] == target){
                return true;
            }
            else if(matrix[line][mid] < target){
                left = mid + 1;
            }
            else{
                right = mid - 1;
            }
        }
        return false;
    }
}

需要稍微处理一下边界条件,因为二分考虑了边界,可能存在越界的情况,行数太大太小都不行。

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if(matrix.empty()) return false;
        int rows=matrix.size();
        int cols=matrix[0].size();
        bool found=false;
        if(rows>0 && cols>0)
        {
            //从第一行最后一个数开始比较
            int row=0;
            int col=cols-1;
            while(row<rows && col>=0)
            {
                if(matrix[row][col]==target)    //找到,返回true
                {
                    found=true;
                    break;
                }
                else if(matrix[row][col]>target)   //当前比target大,此时当前列不符合条件,后退一列
                   --col;
                else                                           //当前比target小,此时当前行不符合条件,往下一行
                    ++row;
            }

        }
        return found;
    }
};

有一个人提供了剑指offer的方法,没有二分,但也是先找行再找列,都在循环里边。


一、滑动窗口

滑动窗口,处理定长数组问题,连续的重复处理过程中往往可以省略中间重复的过程。

二、LeetCode3

无重复字符的最长子串

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int res = 0;
        int cur = 0;
        for(int left = 0, right = 0; right < s.length(); right++){
            for(int i = left; i < right; i++){
                if(s.charAt(i) == s.charAt(right)){
                    left = i + 1;
                    cur = right - left;
                    break;
                }
            }
            cur++;
            res = Math.max(res, cur);
        }
        return res;
    }
}

我觉得自己真棒迅速写出来加一个break,效率还挺高。

三、LeetCode424

替换后的最长重复字符
这题有点意思,替换k个字符,我用上一题类似的方法从左往右,可以得到大部分答案,但是忽略了往前的情况。

class Solution {
    public int characterReplacement(String s, int k) {
        int maxnum = 0;
        int nextleft = 0;
        Boolean temp = false;
        for(int left = 0, right = 0; left + maxnum < s.length(); ){
            int curr = 0;
            right = left = nextleft;
            for(int changenum = 0; changenum <= k && right < s.length(); ){
                if(s.charAt(left) != s.charAt(right)){
                    if(temp == false){
                        temp = true;
                        nextleft = right;
                    }
                    changenum++;
                }   
                if(changenum <= k){
                    curr++;
                    right++;
                }  
            }
            maxnum = Math.max(curr, maxnum);
            temp = false;
        }
        return maxnum;
    }
}

还是看了答案的滑动窗口,滑动窗口也可以看成双指针的应用,当然两者还是有区别的,详见博客

class Solution {
    public int characterReplacement(String s, int k) {
        int maxnum = 0;
        int[] num = new int[26];
        int left = 0, right = 0;
        for(; right < s.length(); right++){
            num[s.charAt(right) - 'A']++;
            maxnum = Math.max(maxnum, num[s.charAt(right) - 'A']);
            if(right - left + 1 - maxnum > k){
                num[s.charAt(left) - 'A']--;left++;
            }
        }
        return right-left;
    }
}

非常巧妙地将滑动的过程用if实现,但是效率好像不高哇可能是循环太多了?

四、LeetCode1004

最大连续1的个数
这题倒是上一题的简化,我这做题的顺序不太合理吧

class Solution {
    public int longestOnes(int[] A, int K) {
        int num = 0;
        int left = 0, right = 0;
        for(; right < A.length; right++){
            if(A[right] == 1)   num++;
            if(right - left + 1 - num > K){
                if(A[left] == 1)    num--;
                left++;
            }
        }
        return right-left;
    }
}

不过没关系,在上一题的基础上简化了一下,达到较高效率。

五、LeetCode1208

尽可能使字符串相等

class Solution {
    public int equalSubstring(String s, String t, int maxCost) {
        int maxlength = 0;
        int cost = 0;
        int left = 0, right = 0;
        int curr = 0;
        for(; right < s.length(); right++){
            cost += Math.abs(t.charAt(right) - s.charAt(right));
            curr++;
            while(cost > maxCost){
                cost -= Math.abs(t.charAt(left) - s.charAt(left));
                left++;curr--;
            }
            maxlength = Math.max(maxlength, curr);
        }
        return maxlength;
    }
}

滑动窗口是问题本身,再选择合适的数据结构。见前面的博客。

六、LeetCode1493

删掉一个元素以后全为 1 的最长子数组

class Solution {
    public int longestSubarray(int[] nums) {
        int num = 0;
        int left = 0, right = 0;
        for(; right < nums.length; right++){
            if(nums[right] == 1)   num++;
            if(right - left + 1 - num > 1){
                if(nums[left] == 1)    num--;
                left++;
            

        }
        return right-left-1;
    }
}

与前两题差不多,稍微改了滑动的判断条件。

七、LeetCode239

滑动窗口最大值
挺好的,我只能想到不使用双指针,要用队列,甚至写了优先队列。
实现三种方法一个都没写出来。
还错把题目看成求最大窗口和。
代码就是题解代码,我写不出来了。

八、LeetCode295

数据流的中位数
这题是我要写的滑动窗口第480题的前身,那题太难了。
这也是中位数的一道题,前面一道中位数还是两个数组,这个都没有,这道题可以用两个堆实现,也就是两个优先队列。

class MedianFinder {
    protected int count;
    protected PriorityQueue<Integer> minheap;
    protected PriorityQueue<Integer> maxheap;

    /** initialize your data structure here. */
    public MedianFinder() {
        count = 0;
        minheap = new PriorityQueue<>();
        maxheap = new PriorityQueue<>((a, b) -> b - a);
    }
    
    public void addNum(int num) {
        count++;
        maxheap.offer(num);
        minheap.offer(maxheap.poll());
        if(count%2 == 1)  maxheap.offer(minheap.poll());
    }
    
    public double findMedian() {
        if(count%2 == 0)  return ((minheap.peek() + maxheap.peek())/2.0);
        else return (double)maxheap.peek();
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

算是又复习了前面的堆。
但是480还是得看解析。。。

九、LeetCode209

长度最小的子数组
还是回来做简单一点的题

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int curr = 0;
        int ans = nums.length;
        int temp = 0;
        int left = 0;
        boolean all = false;
        for(int i = 0; i < nums.length; i++){
            curr++;
            temp += nums[i];
            while(temp >= target){
                all = true;
                ans = Math.min(ans, curr);
                temp -= nums[left++];
                curr--;
            }
        }
        if(all == false)  return 0;
        return ans;
    }
}

涉及连续子数组的问题,我们通常有两种思路:一是滑动窗口、二是前缀和。

十、LeetCode76

最小覆盖子串
这题我能靠到这个思路来,但是还不能一下子代码实现,先统计需要的字符,然后统计当前范围内的字符数,达到要求后通过移动右指针左指针依次找到最小的范围输出。

// class Solution {
//     public String minWindow(String s, String t) {
//         Map<Character, Integer> hash = new HashMap<>();
//         Queue<Character> queue = new LinkedList<>();
//         String ans = new String();
//         for(int i = 0; i < t.length(); i++){
//             hash.put(t.charAt(i), 0);
//         }
//         //找开始的位置
//         int left = -1;
//         for(int i = 0; i < s.length(); i++){
//             if(hash.containsKey(s.charAt(i))){
//                 hash.put(s.charAt(i), 1);
//                 queue.offer(s.charAt(i));
//                 left = i;break;
//             }   
//         }
//         if(left == -1)  return "";
//         if(t.length() == 1)    return String.valueOf(t.charAt(0));
//         //找第一次找全的时候
//         int right = -1;
//         for(int i = left + 1; i < s.length(); i++){
//             if(hash.containsKey(s.charAt(i)) && hash.get(s.charAt(i)) == 0){
//                 queue.offer(s.charAt(i));
//                 hash.put(s.charAt(i), hash.get(s.charAt(i))+1);
//                 if(queue.size() == t.length())  right = i;
//             }
//         }
//         if(right == -1)  return "";
//         //找最短
//         int length = right - left + 1;
//         for(int i = right + 1; i < s.length(); i++){
//             char x = queue.poll();
//             if(s.charAt(i) == x){
//                 if(queue.isEmpty()) break;
//                 while(!hash.containsKey(queue.peek()) || hash.get(queue.peek()) >= 1){
//                     if(hash.containsKey(queue.peek()) && 
//                         hash.get(queue.peek()) >= 1) hash.put(queue.peek(), hash.get(queue.peek()) - 1);
//                     queue.poll();
//                     left++;
//                 }
//                 queue.offer(x);
//                 right++;
//             }
//             if(right - left + 1 < length)   ans = s.substring(left, right);
//         }
//         return s;
//     }
// }


// class Solution {
//     public String minWindow(String s, String t) {
//         if (s.length() < t.length()) {
//             return "";
//         }
//         int[] need = new int[128];
//         int[] have = new int[128];
//         for (int i = 0; i < t.length(); i++) {
//             need[t.charAt(i)]++;
//         }
//         int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
//         while (right < s.length()) {
//             char r = s.charAt(right);
//             if (need[r] == 0) {
//                 right++;
//                 continue;
//             }
//             if (have[r] < need[r]) {
//                 count++;
//             }
//             have[r]++;
//             right++;
//             while (count == t.length()) {
//                 if (right - left < min) {
//                     min = right - left;
//                     start = left;
//                 }
//                 char l = s.charAt(left);
//                 if (need[l] == 0) {
//                     left++;
//                     continue;
//                 }
//                 if (have[l] == need[l]) {
//                     count--;
//                 }
//                 have[l]--;
//                 left++;
//             }
//         }
//         if (min == s.length() + 1) {
//             return "";
//         }
//         return s.substring(start, start + min);
//     }
// }

class Solution {
    Map<Character, Integer> ori = new HashMap<Character, Integer>();
    Map<Character, Integer> cnt = new HashMap<Character, Integer>();

    public String minWindow(String s, String t) {
        int tLen = t.length();
        for (int i = 0; i < tLen; i++) {
            char c = t.charAt(i);
            ori.put(c, ori.getOrDefault(c, 0) + 1);
        }
        int l = 0, r = -1;
        int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
        int sLen = s.length();
        while (r < sLen) {
            ++r;
            if (r < sLen && ori.containsKey(s.charAt(r))) {
                cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
            }
            while (check() && l <= r) {
                if (r - l + 1 < len) {
                    len = r - l + 1;
                    ansL = l;
                    ansR = l + len;
                }
                if (ori.containsKey(s.charAt(l))) {
                    cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
                }
                ++l;
            }
        }
        return ansL == -1 ? "" : s.substring(ansL, ansR);
    }

    public boolean check() {
        Iterator iter = ori.entrySet().iterator(); 
        while (iter.hasNext()) { 
            Map.Entry entry = (Map.Entry) iter.next(); 
            Character key = (Character) entry.getKey(); 
            Integer val = (Integer) entry.getValue(); 
            if (cnt.getOrDefault(key, 0) < val) {
                return false;
            }
        } 
        return true;
    }
}

可以用数组也可用哈希表,但不需要像我第一段还没实现的那段代码一样分步骤,而是直接用一个while实现,看最后的check函数,其实就是判断是否完成全部统计,但是这个专业的写法我有点架不住

十一、LeetCode438

找到字符串中所有字母异位词
这题比上一题稍微简单一点,我以较低效率也算完成了,我觉得思想应该没啥问题,

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ans = new ArrayList<>();
        Map<Character, Integer> hash = new HashMap<>();
        Map<Character, Integer> have = new HashMap<>();
        for(int i = 0; i < p.length(); i++){
            hash.put(p.charAt(i), hash.getOrDefault(p.charAt(i), 0) + 1);
        }
        int left = 0, right = 0, count = 0;
        while(right < s.length()){
            have.put(s.charAt(right), have.getOrDefault(s.charAt(right), 0) + 1);
            if(hash.containsKey(s.charAt(right)) && have.get(s.charAt(right)) <= hash.get(s.charAt(right))){
                count++;
            }
            else if(hash.containsKey(s.charAt(right))){
                for(int i = left; i < right; i++){
                    have.put(s.charAt(i), have.get(s.charAt(i)) - 1);
                    if(s.charAt(i) == s.charAt(right)){
                        left = i + 1;
                        break;
                    }
                    count--;
                }
            }
            else{
                count = 0;
                for(Iterator<Map.Entry<Character, Integer>> it = have.entrySet().iterator() ; it.hasNext();){
                    Map.Entry<Character, Integer> item = it.next();
                    it.remove();
                }
                left = right + 1;
            }
            if(count == p.length()){
                count--;
                have.put(s.charAt(left), have.get(s.charAt(left)) - 1);
                ans.add(left++);
            }
            right++;
        }
        return ans;
    }
}

这道题还有一个收获是哈希表的清空,要用到迭代器且要不断存储下一个结点,与上一题的题解有点同步好像又理解了一点。

十二、LeetCode567

字符串的排列
这题直接用的上一题的代码,判断存在,为什么这题不安排在前面写啊啊啊

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        Map<Character, Integer> hash = new HashMap<>();
        Map<Character, Integer> have = new HashMap<>();
        for(int i = 0; i < s1.length(); i++){
            hash.put(s1.charAt(i), hash.getOrDefault(s1.charAt(i), 0) + 1);
        }
        int left = 0, right = 0, count = 0;
        while(right < s2.length()){
            have.put(s2.charAt(right), have.getOrDefault(s2.charAt(right), 0) + 1);
            if(hash.containsKey(s2.charAt(right)) && have.get(s2.charAt(right)) <= hash.get(s2.charAt(right))){
                count++;
            }
            else if(hash.containsKey(s2.charAt(right))){
                for(int i = left; i < right; i++){
                    have.put(s2.charAt(i), have.get(s2.charAt(i)) - 1);
                    if(s2.charAt(i) == s2.charAt(right)){
                        left = i + 1;
                        break;
                    }
                    count--;
                }
            }
            else{
                count = 0;
                for(Iterator<Map.Entry<Character, Integer>> it = have.entrySet().iterator() ; it.hasNext();){
                    Map.Entry<Character, Integer> item = it.next();
                    it.remove();
                }
                left = right + 1;
            }
            if(count == s1.length())    return true;
            right++;
        }
        return false;
    }
}

虽然效率还是很低。

一、回溯法

类似枚举,按层枚举,结束依次返回上一层递归。

二、LeetCode46

全排列
我感觉题目越来越难了,,,,,
这题就看看答案做的

三、LeetCode47

全排列||
虽然但是,这题我又写出来了,是在上一题的参考代码上加一句话实现。

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        int len = nums.length;
        // 使用一个动态数组保存所有可能的全排列
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }

        boolean[] used = new boolean[len];
        Deque<Integer> path = new ArrayDeque<>(len);

        dfs(nums, len, 0, path, used, res);
        return res;
    }

    private void dfs(int[] nums, int len, int depth,
                     Deque<Integer> path, boolean[] used,
                     List<List<Integer>> res) {
        if (depth == len && !res.contains(new ArrayList<>(path))) {
            
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < len; i++) {
            if (!used[i]) {
                path.addLast(nums[i]);
                used[i] = true;

                dfs(nums, len, depth + 1, path, used, res);

                used[i] = false;
                path.removeLast();
            }
        }
    }
}

当然可想而知这样是非常划不来的,解析给了剪枝的思想,对这一类重复的部分跳过不操作,要排序与前一位比较并判断布尔数组是否是我们想要的,很巧妙,下次一定。

四、LeetCode39

组合总和
这题算子集,可以含重复元素,找到其可以回溯的模块,可以写出来。

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> list = new ArrayList<>();
        //每判断一个找子集
        Deque<Integer> stack = new ArrayDeque<>();
        search(list, 0, target, candidates, stack);
        return list;
    }
    public void search(List<List<Integer>> list, int start, int target, int[] candidates, Deque stack){
        if(target == 0){
            list.add(new ArrayList<>(stack));
            return;
        } 
        if(start >= candidates.length)  return;
        while(candidates[start] > target){
            start++;
            if(start >= candidates.length)  return;
        }   
        stack.addLast(candidates[start]);
        target -= candidates[start];
        search(list, start, target, candidates, stack);
        stack.removeLast();
        target += candidates[start];
        search(list, start + 1, target, candidates, stack);
        return;
    }
}

还不错哦
以及他的延伸40题,这题数组中可能有重复元素,但一个元素不能重复用,所以要加个判断条件,同时将数组排个序,防止因顺序出现的重复组合。
又看了第78和90两个子集的题目,这两题之前数组写过就没重复写,总的来说回溯是一种遍历,可以剪枝来简化,处理这一类排列组合的题很方便,但是这两题其实我用的并不是回溯,调用若干次函数,反而像迭代。

五、LeetCode31

下一个排列
这题是为接下来的60题打基础,是全排列的题,这其中或许涉及数学问题我之前就没弄明白。

class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        for (int i = len - 1; i > 0; i--) {
                if (nums[i] > nums[i - 1]) {
                    Arrays.sort(nums, i, len);
                    for (int j = i; j <len; j++) {
                        if (nums[j] > nums[i - 1]) {
                            int temp = nums[j];
                            nums[j] = nums[i - 1];
                            nums[i - 1] = temp;
                            return;
                        }
                    }
                }
            }
    	Arrays.sort(nums);
		return;  
    }
}

六、LeetCode60

排列序列
这题继承全排列那题的话,可以用46的回溯取第n个或者用上一题的函数取或者用数学方法,即所谓剪枝。

七、LeetCode93

复原IP地址
这题也是对字符串的处理,我这好像不是标准的回溯,因为没有返回上一层,可以做出来,但是效率不高。

class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> list = new ArrayList<>();
        if(s.length() <= 3)   return list;
        search(s, 0, list, 0, "");
        return list;
    }
    public void search(String s, int num, List<String> list, int index, String ss){
        if(num == 3){
            if((index + 1 < s.length() && s.charAt(index) == '0' ) || 
                s.length() - index > 3 || 
                Integer.parseInt(s.substring(index)) > 255) return;
            else    list.add(ss + s.substring(index));
        }
        if(index + 1 < s.length() && s.charAt(index) == '0')  
            search(s, num + 1, list, index + 1, ss + s.substring(index, index + 1) + ".");
        else{
            if(index + 1 < s.length())    search(s, num + 1, list, index + 1, ss + s.substring(index, index + 1) + ".");
            if(index + 2 < s.length())    search(s, num + 1, list, index + 2, ss + s.substring(index, index + 2) + ".");
            if(index + 3 < s.length() && Integer.parseInt(s.substring(index, index + 3)) <= 255)    
                search(s, num + 1, list, index + 3, ss + s.substring(index, index + 3) + ".");
        }
        return;
    }
}

八、LeetCode529

扫雷游戏
这道题要每一步都处理其四周八个位置的雷区,但是按照回溯的思想也很好解决,我已经能独立解决这类问题了耶

class Solution {
    public char[][] updateBoard(char[][] board, int[] click) {
        if(board[click[0]][click[1]] == 'M'){
            board[click[0]][click[1]] = 'X';
            return board;
        }    
        int[][] visited = new int[board.length][board[0].length];
        search(board, click[0], click[1], visited);
        return board;
    }
    public void search(char[][] board, int x, int y, int[][] visited){
        if(x >= 0 && y >= 0 && x < board.length && y < board[0].length && visited[x][y] == 0){
            visited[x][y] = 1;
            if(board[x][y] == 'E'){
                if(Mxy(board, x, y) > 0){
                    board[x][y] = (char)('0' + Mxy(board, x, y));
                    return;
                } 
                else{
                    board[x][y] = 'B';
                    search(board, x - 1, y, visited);
                    search(board, x - 1, y - 1, visited);
                    search(board, x - 1, y + 1, visited);
                    search(board, x + 1, y, visited);
                    search(board, x + 1, y - 1, visited);
                    search(board, x + 1, y + 1, visited);
                    search(board, x, y + 1, visited);
                    search(board, x, y - 1, visited);
                } 
            }  
        }
        return;
    }
    public int Mxy(char[][] board, int x, int y){
        return boom(board, x - 1, y) + boom(board, x - 1, y - 1) + boom(board, x - 1, y + 1) + boom(board, x + 1, y)
         + boom(board, x + 1, y - 1) + boom(board, x + 1, y + 1) + boom(board, x, y + 1) + boom(board, x, y - 1);
    }
    public int boom(char[][] board, int x, int y){
        return (x >= 0 && y >= 0 && x < board.length && y < board[0].length && board[x][y] == 'M')?1:0;
    }
}

九、LeetCode22

括号生成
这个题按某答主的意思可以看出DFS较BFS的优越性,在这种题型里,DFS不用手动显式用到栈或队列,而用系统的栈式结构实现,且BFS要自己定义结点类。

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> list = new ArrayList<>();
        DFS("", list, n, n);
        return list;
    }
    public void DFS(String s, List<String> list, int left, int right){
        if(left < 0 || left > right)    return;
        if(left == 0 && right == 0){ list.add(s);return;}
        DFS(s + '(', list, left - 1, right);
        DFS(s + ')', list, left, right - 1);
        return;
    }
}

再参考一个妹妹的C++代码,这样写很好看了

十、LeetCode51

N皇后
这也是一道游戏题,和扫雷思想差不多
其实很容易想到思路,要枚举嘛,自然可以用回溯遍历同时删掉许多不必要的步骤,这题就是要对行列斜做一下条件筛选,代码我懒得写了差不多这个意思吧


一、BFS

广度优先搜索
用一个队列对二叉树进行层次遍历,一般都是这种题,或者其他类型,但都是用队列实现。

二、LeetCode102

二叉树的层次遍历

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root == null)    return list;
        List<Integer> llist = new ArrayList<>();
        llist.add(root.val);list.add(new ArrayList<>(llist));llist.clear();
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode depart = new TreeNode();
        queue.offer(root);depart.val = -9999;
        queue.offer(depart);
        while(!queue.isEmpty() && queue.peek().val != -9999){
            TreeNode temp = queue.poll();
            if(temp.left != null){
                llist.add(temp.left.val);
                queue.offer(temp.left);
            }  
            if(temp.right != null){
                llist.add(temp.right.val);
                queue.offer(temp.right);
            }  
            if(queue.peek().val == -9999){
                if(!llist.isEmpty())  list.add(new ArrayList<>(llist));
                llist.clear();
                queue.offer(queue.poll());
            }
        }
        return list;
    }
}

题解里并没有像我一样用标记(毕竟标记的结点数值并不一定不有效),而是循环队列长度次数,以逐层输出。

三、LeetCode107

二叉树的层序遍历II
通过上一题,这里先将答案存在List栈里,最后挨个push到list中可以实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> list = new ArrayList<>();
        if(root == null)    return list;
        Deque<List<Integer>> stack = new LinkedList<>();
        List<Integer> llist = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode depart = new TreeNode();
        queue.offer(root);depart.val = -9999;
        queue.offer(depart);
        while(!queue.isEmpty() && queue.peek().val != -9999){
            TreeNode temp = queue.poll();
            if(temp.left != null){
                llist.add(temp.left.val);
                queue.offer(temp.left);
            }  
            if(temp.right != null){
                llist.add(temp.right.val);
                queue.offer(temp.right);
            }  
            if(queue.peek().val == -9999){
                if(!llist.isEmpty())  stack.addLast(new ArrayList<>(llist));
                llist.clear();
                queue.offer(queue.poll());
            }
        }
        while(!stack.isEmpty()){
            list.add(stack.removeLast());
        }
        llist.add(root.val);list.add(new ArrayList<>(llist));
        return list;
    }
}

如果直接102翻转加Collections.reverse(list);即可

四、LeetCode200

岛屿问题
这题很巧妙把1在判断之后变为0
好像之前碰到过
于是广搜一下就出来了

class Solution {
    public int numIslands(char[][] grid) {
        int res = 0;
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j <grid[0].length; j++){
                if(grid[i][j] == '1'){
                    res++;
                    bfs(grid, i, j);
                }   
            }
        }
        return res;
    }
    public void bfs(char[][] grid, int i, int j){
        if(i >= 0 && j >= 0 && i < grid.length && j < grid[0].length && grid[i][j] == '1'){
            grid[i][j] = '0';
            bfs(grid, i + 1, j);
            bfs(grid, i - 1, j);
            bfs(grid, i, j + 1);
            bfs(grid, i, j - 1);
        }  
        return;
    }
}

内存居然用了这么多吗?之后还会写到这题,再说吧


一、DFS

深度优先搜索
回溯最原始的递归过程

二、LeetCode78

子集

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> include = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        dfs(include, nums, 0, list);
        return include;
    }
    public void dfs(List<List<Integer>> include, int[] nums, int index, List<Integer> list){   
        if(index >= nums.length){
            include.add(new ArrayList(list));
            return;
        }
        list.add(nums[index]);
        dfs(include, nums, index + 1, list);
        list.remove(list.size()-1);
        dfs(include, nums, index + 1, list);
        return;
    }
}

这次用DFS重写这道题,DFS与回溯原理相同,都是基于递归实现,其中回溯相较于DFS会更简便一些因为在递归中回溯已经提前终止了一些不必要的操作

三、LeetCode100

相同的树
这题很有意思,一眼能看出来做法,卡在最后不知道怎么判断两个list相等,最后还得换个方法,不过换的方法只用判断个别情况,不满足直接结束还节省了时间。

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null)  return true;
        if(p == null || q == null)  return false;
        if(p.val != q.val)  return false;
        return isSameTree(p.left, q.left)&&isSameTree(p.right, q.right);
    }
}

四、LeetCode98

验证二叉搜索树
于是在解析里看到关于二叉树的解题框架

class Solution {
    public boolean isValidBST(TreeNode root) {
        return isValidBST(root, null, null);
    }
    public boolean isValidBST(TreeNode root, TreeNode maxleft, TreeNode minright) {
        if(root == null)    return true;
        if(maxleft != null && root.val >= maxleft.val)  return false;
        if(minright != null && root.val <= minright.val)  return false;
        return isValidBST(root.left, root, minright) && isValidBST(root.right, maxleft, root);
    }
}

选做了这道二叉树的题,因为我感觉那个人好像写错了,既然左边要一直小右边要一直大,那判断的条件也应该与这反过来。

五、LeetCode200

岛屿问题

看了一下这几题的解题思路,果然一篇好的注解文章值得深入研究。
这一题我发现之前写的其实已经是今天的DFS方法,而那个BFS方法是要借助一个队列依次将符合要求的陆地存进去再广度遍历。

六、LeetCode721

账户合并
看这个视频吧:传送门
这题目还贴近真实工作,还麻烦,还写不出来还不愿意写。。。
我先把他看懂吧,反正并查集还会遇到这题
先把所有的邮箱建图,以第一个邮箱为中心双向互指,这时还不用指名字,然后遍历(dfs),遍历的意义就是找重复的邮箱,你看我们已经把每个单独的list以图的形式连起来了,那当你发现图里面有你当前搜索到的邮箱说明了什么?说明这两个人是同一个人,那同一个人就不用重复存,而选择dfs这个邻居,遍历这个邻居连接的所有未被处理过的邻居,直到把所有的该连的都连起来。
这样依次处理将能连起来的都连起来,外面还要加一层循环遍历每个拥有名字的list,这样题目要求就能实现了。


一、分治法

将一个大问题分割为几个小问题逐一解决再合并为大问题,是这种方法的主要思想,
例子:归并排序

二、LeetCode169

多数元素
这题是一道简单题,最开始想到的是哈希表但是感觉会比较麻烦,添加后还要找最大,然后想到用类似双指针的思想先排序再统计,根据题目的特殊性做出来。

class Solution {
    public int majorityElement(int[] nums) {
        if(nums.length <= 2)    return nums[0];
        Arrays.sort(nums);
        return search(nums, 0);
    }
    public int search(int[] nums, int index){
        int temp = 1;
        while(index == nums.length - 1 || nums[index++] == nums[index]){
            temp++;
            if(temp > nums.length/2)    return nums[index];
        }
        return search(nums, index);
    }    
}

那答案呢自然提供了哈希表的求法,为了方便找最大值,第二次见到了Map.Entry的用法,可以参见这篇博客传送门
感觉还挺巧妙的。
但是看到自己这个方法,官方称之为排序,而且直接返回数组第n/2个即可,等于我后面那些其实都能省掉。
这道题的分治和投票都很值得回味。

三、LeetCode53

最大子序列和
这题真的要用分治吗
最先想到的又是双指针和滑动窗口,答案的贪心和动态规划也能勉强看懂,这分治也太抽象了

class Solution {
    public int maxSubArray(int[] nums) {
        return maxsum(nums, 0, nums.length - 1);
    }
    public int maxsum(int[] nums, int left, int right){
        if(left == right)   return nums[left];
        int mid = (left + right)/2;
        int maxLeft = -99999;
        for (int i = mid, sum = 0; i >= left; --i) {
            sum += nums[i];
            maxLeft = Math.max(maxLeft, sum);
        }
        int maxRight = -99999;
        for (int i = mid + 1, sum = 0; i <= right; ++i) {
            sum += nums[i];
            maxRight = Math.max(maxRight, sum);
        }
        int maxCross = maxLeft + maxRight;
        int maxInA = maxsum(nums, left, mid), maxInB = maxsum(nums, mid + 1, right);
        return Math.max(Math.max(maxInA, maxInB), maxCross);
    }
}

在解析里看到一种分治的实现,这样看来有点类似于二分,对左边处理,对右边处理,对中间处理,返回需要的最佳情况。

四、LeetCode215

第k大的值,
这题与703有什么区别吗
连解析都没找到分治的是题目分错类了吧

class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        int size = k;
        for(int num:nums){
            queue.add(num); 
            if(queue.size() > size)    queue.poll();
        }  
        return queue.peek();
    }
}

一、递归

二、LeetCode687

最长同值路径
这题统计最长的同值的路径,题目好像没有说清楚这个路径是什么意思,如果按照至多一个拐点来算可以参考某个博主的代码

class Solution {
    public int longestUnivaluePath(TreeNode root) {
        int ans = 0;
        longestUnivaluePath(root, ans);
        return ans;
    }
    public int longestUnivaluePath(TreeNode root, int ans) {
        //理解要递归的部分
        //为空时返回0
        if(root == null)    return 0;
        //从下而上统计子路径
        int left = longestUnivaluePath(root.left, ans);
        int right = longestUnivaluePath(root.right, ans);
        left = (root.left != null && root.left.val == root.val)?left + 1:0;
        right = (root.right != null && root.right.val == root.val)?right + 1:0;
        //ans是最长路径,可能是子路径也可能是经过根结点的
        ans = Math.max(ans, left + right);
        //返回值是单边,左或者右的最大值
        return Math.max(left, right);
    }
}

但是我没提交,样例都没过,我觉得题目表达不清楚

三、LeetCode124

二叉树中的最大路径和

class Solution {
    int ans = -9999;
    public int maxPathSum(TreeNode root) {
        longestUnivaluePath(root);
        return ans;
    }
    public int longestUnivaluePath(TreeNode root) {
        //理解要递归的部分
        //为空时返回0
        if(root == null)    return 0;
        //从下而上统计子路径
        int left = Math.max(longestUnivaluePath(root.left), 0);
        int right = Math.max(longestUnivaluePath(root.right), 0);
        //ans是最长路径,可能是子路径也可能是经过根结点的
        ans = Math.max(ans, left + right + root.val);
        // System.out.println(left+" "+right+" "+ans);
        //返回值是单边,左或者右的最大值
        return Math.max(left, right) + root.val;
    }
}

这说是跟刚刚那题差不多,其实还是要改一部分,主要是写一个递归函数,把答案定义为全局变量,只有在路径大于0时才有加进答案的必要

三、LeetCode783

二叉搜索树节点最小距离
题目要求的是任意两点,对于比大小将树按序排列最小值必然是相邻中取得,所以要么排序要么中序递归

class Solution {
    int ans, pre;
    public int minDiffInBST(TreeNode root) {
        ans = Integer.MAX_VALUE;
        pre = -100;
        search(root);
        return ans;
    }
    public void search(TreeNode root){
        if(root == null)    return;
        search(root.left);
        ans = Math.min(ans, root.val - pre);
        pre = root.val;
        search(root.right);
    }
}

这题额外的收获是关于最大最小值的使用,double中的最小值是最小的非负正数,见博客传送门
实验说明将变量定义为定值比定义为MAX_VALUE占用内存大,即可以把pre定义为MAX_VALUE在递归中加上绝对值的判断,可节省一定内存。
此外,递归还看到这么一篇博客供参考学习:传送门
博客里抽做的几题第一次都不能独立完成。。。


一、拓扑排序

拓扑排序处理存在前后逻辑即是否成环的问题,最常见的就是课程表的问题。
常用到dfs和bfs实现。

二、LeetCode207

判断是否可以,选择用bfs实现的话,要借助一个队列,其实是依次记录入度为0的结点,若节点数与总数相同则可以,不等说明有环;如果用dfs每次终止的条件为访问到了前面正在访问的出度不为零的结点,也就是形成了环。

三、LeetCode210

在上一题的基础上将每次的结点存下来,如果可以就输出,不可以就新建一个空数组输出。


一、Tries

字典树,或者说前缀树,空间换时间。
在数据结构的树接触的时候这个不复杂,但是用这个单独做题直接忘记结点类导致一下子没法下手。

二、LeetCode720

词典中最长的单词
思想能想到,像上面说的忘记结点类的定义,所以参考了一下别人的代码。

class Solution {
    int longestLen = 0;
    String ansLongerWord = "";
    public String longestWord(String[] words) {
        TrieNode root = new TrieNode();
        for (String s :words) {
            TrieNode cur = root;
            for (char c : s.toCharArray()) {
                if (cur.children[c - 'a'] == null)
                    cur.children[c - 'a'] = new TrieNode();
                cur = cur.children[c - 'a'];
            }
            cur.isEnd = true;
            cur.word = s;
        }
        longestWordDFS(root, 0);
        return ansLongerWord;
    }    
    public void longestWordDFS(TrieNode root, int depth) {
        if (root == null || (depth > 0 && !root.isEnd)) //当前节点为空,或者当前节点(非根节点)不是单词的结尾时,return剪枝
            return;        
        //每次搜索更新最大深度和最长单词
        if (depth > longestLen) {
            longestLen = depth;
            ansLongerWord = root.word;
        }        
        for (TrieNode node : root.children) 
            if (node != null)
                longestWordDFS(node, depth + 1);
    }
}
class TrieNode {
    String word;
    TrieNode[] children;
    boolean isEnd;

    public TrieNode() {
        children = new TrieNode[26];
        isEnd = false;
    }
}

存完后深度遍历,得到最长的值。

三、LeetCode211

添加与搜索单词–数据结构设计

class WordDictionary {
    class TrieNode {
        TrieNode[] son;
        boolean isEnd;

        public TrieNode() {
            son = new TrieNode[26];
            isEnd = false;
        }
    }
    protected TrieNode root;

    /** Initialize your data structure here. */
    public WordDictionary() {
        root = new TrieNode();
    }
    
    public void addWord(String word) {
        TrieNode curr = root;
        for(char a:word.toCharArray()){
            if(curr.son[a - 'a'] == null)   curr.son[a - 'a'] = new TrieNode();
            curr = curr.son[a - 'a'];
        }
        curr.isEnd = true;
    }
    
    public boolean search(String word) {
        return search(word, root, 0);
    }

    public boolean search(String word, TrieNode node, int start) {
        if(start == word.length())  return node.isEnd;
        if(word.charAt(start) == '.'){
            for(int i = 0; i < 26; ++i){
                if(node.son[i] != null && search(word, node.son[i], start+1) == true)    return true;
            }
            return false;
        }
        else if(node.son[word.charAt(start) - 'a'] == null)   return false;   
        return search(word, node.son[word.charAt(start) - 'a'], start+1);
    }
}

/**
 * Your WordDictionary object will be instantiated and called as such:
 * WordDictionary obj = new WordDictionary();
 * obj.addWord(word);
 * boolean param_2 = obj.search(word);
 */

对结点类的定义可以放在类里边,这题的思路也比较好想,模糊查找的时候要遍历所有子节点,便利的方法是循环26次,所以要写成另一个方法,也不用调用一次搜索完,每次搜索完一个位置就可以了。

四、LeetCode676

实现一个魔法字典
这题好可恶啊,存在hello,hallo然后搜索hello为true的情况,但是我的前缀树如果能找到hello就返回false了,这咋在中间还发现可以分个支找到hallo的?!有点不甘心看答案,下次再写吧。
错误代码:

class MagicDictionary {
    class TrieNode {
        TrieNode[] son;
        boolean isEnd;

        public TrieNode() {
            son = new TrieNode[26];
            isEnd = false;
        }
    }
    protected TrieNode root;
    /** Initialize your data structure here. */
    public MagicDictionary() {
        root = new TrieNode();
    }
    public void addWord(String word) {
        TrieNode curr = root;
        for(char a:word.toCharArray()){
            if(curr.son[a - 'a'] == null)   curr.son[a - 'a'] = new TrieNode();
            curr = curr.son[a - 'a'];
        }
        curr.isEnd = true;
    }    
    public void buildDict(String[] dictionary) {
        for(String s: dictionary) addWord(s);
    }
    public boolean search(String word) {
        return search(word, root, 0, false);
    }
    public boolean search(String word, TrieNode node, int start, boolean haschange) {
        if(start == word.length())  return haschange && node.isEnd;
        if(node.son[word.charAt(start) - 'a'] == null){
            if(haschange == false){
                haschange = true;
                for(int i = 0; i < 26; ++i){
                    if(node.son[i] != null && search(word, node.son[i], start+1, haschange) == true)    return true;
                }
            }
            return false;  
        } 
        return search(word, node.son[word.charAt(start) - 'a'], start+1, haschange);
    }
}

欧我写出来了,细想一下,对于上述的特殊情况,不用搜索这个单词的全部,而是每一步考虑改与不改两种情况,这样会多一些26循环,然后一旦满足条件就返回true。

    public boolean search(String word, TrieNode node, int start, boolean haschange) {
        if(start == word.length())  return haschange && node.isEnd;
        if(node.son[word.charAt(start) - 'a'] == null){
            if(haschange == false){
                haschange = true;
                for(int i = 0; i < 26; ++i){
                    if(node.son[i] != null && search(word, node.son[i], start+1, haschange) == true)    return true;
                }
            }
            return false;  
        } 
        else if(haschange == false){
            for(int i = 0; i < 26; ++i){
                if(i != word.charAt(start) - 'a' && node.son[i] != null
                 && search(word, node.son[i], start+1, true) == true)   
                    return true;
            }
        } 
        return search(word, node.son[word.charAt(start) - 'a'], start+1, haschange);
    }
}

一、并查集

Union Find
合并+查找,适合寻根的问题,重要的一个合并一个查找,合并时将根结点改为一样的,优化时改为高度更高的那个根结点值,相等则改的同时要给根结点高度+1。
并查集有模板,知道能采用这个方法再套一套应该为题不大。

二、LeetCode200

岛屿数量,这道题之前用深搜和广搜做的,用并查集不用把1变为0,开始的岛屿数是所有1的个数,随着合并指向相同的根二逐渐减小count值,最后getcount输出。
很巧妙但是代码我懒得写了哈哈

三、LeetCode547

省份数量
假装上一题是我自己写出来的,那借用union和find函数,改变一下合并的条件其他几乎都不用变就可以解决。
并查集能解决的问题一般都能用dfs和bfs解决,那这两者的区别一个调用n次一个调用一次,且bfs最大的不同就是用到了一个队列,其他都用一个visited数组标记是否访问即可,最终的话还是并查集比较方便。

class Solution {
    int count;
    int[] parent;
    int[] rank;
     
    public int findCircleNum(int[][] isConnected) {
        parent = new int[isConnected.length];
        rank = new int[isConnected.length];
        count = isConnected.length;
        for(int i = 0; i < isConnected.length; ++i){
            parent[i] = i;
            rank[i] = 0;
        } 
        for(int i = 0; i < isConnected.length; ++i){
            for(int j = 0; j < i; ++j)  
                if(isConnected[i][j] == 1)    union(i, j);
        }
        return count;
    }
    public int find(int i) {
        if (parent[i] != i) parent[i] = find(parent[i]);
        return parent[i];
    }
    public void union(int x, int y) {
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {
            if (rank[rootx] > rank[rooty]) {
                parent[rooty] = rootx;
            } else if (rank[rootx] < rank[rooty]) {
                parent[rootx] = rooty;
            } else {
                parent[rooty] = rootx;
                rank[rootx] += 1;
            }
            --count;
        }
    }
    
}

四、LeetCode721

账户合并
这题之前在dfs碰到过,当时是用建图然后互指balabala解决,现在用并查集只需将他们合并。。。。
好吧代码我还是写不出来,但看思路是将所有的邮件编号存储一遍,然后挨个查找将两者合并,这样重复的就会合并多次即把共name的邮箱连在了一起,overover

五、LeetCode130

被围绕的区域
这题也是连通图的问题,跟岛屿那个有点像,这里单独考虑边界不能变的区域,其他都为X,所以同样的也可以用bfs和dfs做,但是用并查集将所有边界和与边界相连的0都指向自定义的任意一个根,中间没连上的都可以填海造陆,从而实现。


一、动态规划

我现在的理解是当前状态与之前有限状态有关,那么可以递推得到我们要的状态值。

二、LeetCode70

爬楼梯
这题真狠,递归居然会超时

class Solution {
    public int climbStairs(int n) {
        if(n == 0)  return 1;
        if(n < 0)   return 0;
        return climbStairs(n-1) + climbStairs(n-2);
    }
}

答案给了一种类似滑动窗口又有点不像的动态方法,

class Solution {
    public int climbStairs(int n) {
        int fn2 = 0, fn1 = 0, fn = 1;
        for(int i = 1; i <= n; ++i){
            fn2 = fn1;
            fn1 = fn;
            fn = fn2 + fn1;
        }
        return fn;
    }
}

开始初始化了0,-1的状态,之后根据n的值累加,这好像是一种经典的问题(背包问题?)找时间了解一下。

三、LeetCode121

买卖股票的最佳时机

class Solution {
    public int maxProfit(int[] prices) {
        int in = Integer.MAX_VALUE, out = 0, res = 0;
        for(int i = 0; i < prices.length; ++i){
            if(prices[i] < in){
                in = prices[i];
                out = 0;
            }  
            else if(prices[i] > out){
                out = prices[i];
                res = Math.max(res, out - in);
            } 
        }
        return res;
    }
}

我好像没有弄懂动态规划是要我干个什么就把这题写出来了。

public class Solution {

    public int maxProfit(int[] prices) {
        int len = prices.length;
        // 特殊判断
        if (len < 2) {
            return 0;
        }
        int[][] dp = new int[len][2];

        // dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
        // dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数

        // 初始化:不持股显然为 0,持股就需要减去第 1 天(下标为 0)的股价
        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        // 从第 2 天开始遍历
        for (int i = 1; i < len; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
        }
        return dp[len - 1][0];
    }
}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个作者提供了一个dp的做法,我感觉和我那个本质上也差不多,但更贴近实际情况,而且好像也属于一类神奇的背包问题。。。
这里的dp是对状态的记录,动态规划就是将题目转换为dp之间转换的问题,从而利用数学逻辑解决实际问题。
下面几题尝试用dp数组解决一下。

四、LeetCode5

最长回文子串
这题我直接看了答案,因为直接写的话我只想到诸如双指针或者双重循环加栈的方法想想就很复杂,于是先看了一眼别人的动态规划,这里的dp依旧是记录状态,大的方向有动态规划和中心拓展两种,再自己做一遍得到。

class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        String ans = new String();
        for(int length = 0; length < n; ++length){
            for(int left = 0; left + length < n; ++left){
                if(length == 0)   dp[left][left] = true;
                else if(length == 1)    dp[left][left + length] = s.charAt(left) == s.charAt(left + length); 
                else    dp[left][left + length] = (s.charAt(left) == s.charAt(left + length)) && dp[left+1][left+length-1];
                if(dp[left][left+length] && length >= ans.length())  ans = s.substring(left, left + length + 1);
            }
        }
        return ans;
    }
}

五、LeetCode1143

最长公共子序列

class Solution {
    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){
            for(int j = 1; j <= text2.length(); ++j){
                if(text1.charAt(i-1) == text2.charAt(j-1))  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()];
    }
}

这道题的dp数组用来记录当前子序列的最大长度,其考虑了两边各是否考虑的三种情况。注意这里双重循环的index从1开始,这样有利于dp数组初始化后对第一个位置的计算。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值