分治算法

作者: Tim在路上 | 来源:发表于2019-11-30 13:58 被阅读0次

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

    // 二分查找的思路,halfLen 是中位数的right 所以必须 m + n + 1
    // 中位数是可以将数组分割为左右相等的数组,一个数将其分为左右相等个数有很多
    // 同时要满足坐最大值小于右最小值

    int m = nums1.length;
            int n = nums2.length;
            if(m > n){
                return findMedianSortedArrays(nums2, nums1);
            }
            // 两个数组中找中位数转换为一个数组
            int left = 0,right = m;
            // 这里用 m + n 表示 大于中间值一位   
            int halfLen = (m + n + 1)/2 ;
            int i = 0,j = 0;
            while(left <= right){
                i = left + (right - left)/2;
                j = halfLen - i;
                if(i < right && nums1[i] < nums2[j-1] ){
                    left = i + 1;
                }else if(i > left && nums1[i-1] > nums2[j]){
                    right = i -1;
                }else{
                    // 找到 最大的left 和 最小right
                    int maxLeft = 0;
                    if(i == 0){
                        maxLeft = nums2[j-1];
                    }else if(j == 0){
                        maxLeft = nums1[i-1];
                    }else{
                        maxLeft = Math.max(nums1[i-1], nums2[j-1]);
                    }
                    if((m+n)%2 == 1) return maxLeft;
                    int minRight = 0;
                    if (i == m) { minRight = nums2[j]; }
                    else if (j == n) { minRight = nums1[i]; }
                    else { minRight = Math.min(nums2[j], nums1[i]); }
                    
                    // 返回 (left + right) / 2.0
                    return (maxLeft + minRight) / 2.0;
                }
    
            }
            return 0.0;
    
    

    // 分治思想找到 两个数组的中位数,实际转换为分治求两个数组第k小的问题

       // 分治思想
        // 中位数实际就是第K小问题,当奇数时是中间数,当偶数是中间两数除以2
        public double findMedianSortedArrays(int[] nums1, int[] nums2) {
            int m = nums1.length;
            int n = nums2.length;
            // 求 k 1 2 3 4  1 2 3 4 5 
            int left = (m + n + 1)/2;
            int right = (m + n + 2)/2;
            return (getMinThK(nums1,0,m-1,nums2,0,n-1,left) +
                        getMinThK(nums1,0,m-1,nums2,0,n-1,right))/2.0;
        }
    
        public int getMinThK(int[] A,int s1,int e1,int[] B,int s2,int e2,int k){
            int len1 = e1 - s1 + 1;
            int len2 = e2 - s2 + 1;
            // 两个数组求第k小,只操作第一个数组
            if(len1 > len2){
                return getMinThK(B, s2, e2, A, s1, e1, k);
            }
            // 默认第一个数组是长度较小数组
            if(len1 == 0){
                return B[s2 + k - 1];
            }
            // 最小,两个数组最小
            if(k == 1){
                return Math.min(A[s1], B[s2]);
            }
            // 每次排除一半,找到两个数组中 k/2 位置
            int lk = s1 + Math.min(len1, k/2) - 1;
            int rk = s2 + Math.min(len2, k/2) - 1;
            // 这两个位置 谁小说明 小于它的一定不会是第k小
            if(A[lk]<B[rk]){
                return getMinThK(A, lk+1, e1, B, s2, e2, k - (lk - s1 + 1));
            }else{
                return getMinThK(A, s1, e1, B, rk+1, e2, k - (rk - s2 + 1));
            }
        }
    

    合并K个有序的链表

    // 分治思想,先两个合并,在两两合并

    使用 interval 来控制合并的数组,将数据都合并到第一个链表上, interval * 2 来表示下一组,停止条件是interval => n

    public ListNode mergeTwoLists(ListNode l1, ListNode l2){
            if (l1 == null){
                return l2;
            }else if (l2 == null){
                return l1;
            }
            ListNode root = new ListNode(-1);
            ListNode p = root;
            while (l1 != null && l2 != null){
                if (l1.val < l2.val){
                    p.next = new ListNode(l1.val);
                    l1 = l1.next;
                }else {
                    p.next = new ListNode(l2.val);
                    l2 = l2.next;
                }
                p = p.next;
            }
            p.next = l1 != null ? l1 : l2;
            return root.next;
        }
    
        public ListNode mergeKLists(ListNode[] lists) {
           if (lists == null || lists.length == 0){
               return null;
           }
           if (lists.length == 1){
               return lists[0];
           }
           // 进行 两两合并
            // 间隔呈现倍数增长
           int interval = 1;
           while (interval < lists.length) {
               for (int i = 0; i < lists.length-interval; i += interval * 2) {
                   lists[i] = mergeTwoLists(lists[i],lists[i+interval]);
               }
               interval *=2;
           }
           return lists[0];
        }
    

    最大子序数组和

    暴力

     public int maxSubArray(int[] nums) {
            if (nums == null || nums.length == 0){
                return 0;
            }
            int n = nums.length;
            // 加1 的目的是让其可以有办法得到其本身
            int[] sums = new int[n+1];
            sums[0] = 0;
            for (int i=1;i<=n;i++){
                sums[i] = sums[i-1] + nums[i-1];
            }
            int max = Integer.MIN_VALUE;
            for (int i=1;i<=n;i++){
                for (int j=0;j<i;j++){
                    System.out.println(sums[i] - sums[j]);
                    if (sums[i] - sums[j] > max){
                        max = sums[i] - sums[j];
                    }
                }
            }
            return max;
        }
    

    // dp 如果前面的sum > 0 才会对后面的和产生增益,否则 不会 sum = num

     public int maxSubArray(int[] nums) {
            int ans = nums[0];
            int sum = 0;
            for(int num: nums) {
                if(sum > 0) {
                    sum += num;
                } else {
                    sum = num;
                }
                ans = Math.max(ans, sum);
            }
            return ans;
        }
    

    // 分治,使用分治的方法进行计算局部最大和

        public int maxSubArray(int[] nums){
            if (nums == null || nums.length == 0){
                return 0;
            }
            int n = nums.length;
            return maxSubArraySum(nums,0,n-1);
        }
    
        public int maxSubArraySum(int[] nums,int left,int right){
            if (left == right){
                return nums[left];
            }
            int mid = (left + right) >>> 1
            return max3(maxSubArraySum(nums,left,mid),maxSubArraySum(nums,mid+1,right),maxCrossingSum(nums,left,mid,right));
        }
    
        public int maxCrossingSum(int[] nums,int left,int mid,int right){
            int sum = 0;
            int sumLeft = Integer.MIN_VALUE;
            for (int i = mid;i>= left;i--){
                sum += nums[i];
                if (sum > sumLeft){
                    sumLeft = sum;
                }
            }
            
            int sumRight = Integer.MIN_VALUE;
            sum = 0;
            for (int i=mid+1;i<=right;i++){
                sum += nums[i];
                if (sum > sumLeft){
                    sumLeft = sum;
                }
            }
            return sumLeft + sumRight;
        }
    

    求众数

    // 众数一定可以抵消调其他数,因为其大于n/2

      // 众数是 一次遍历, 记录上一个值,相同加1,不同减一,当为0 时重新设置 1,更新当前值
        public int majorityElement(int[] nums) {
            int count = 0;
            int majorNum = nums[0];
            for (int i=0;i<nums.length;i++){
                if (count == 0){
                    count = 1;
                    majorNum = nums[i];
                }else {
                    if (nums[i] == majorNum){
                        count ++;
                    }else {
                        count --;
                    }
                }
            }
            return majorNum;
        }
    

    // 使用分治算法

      public int majorityElement(int[] nums){
                return majorityElement(nums,0,nums.length-1);
            }
            
            public int majorityElement(int[] nums,int left,int right){
                if (left == right){
                    return nums[left];
                }
                
                int mid = (left + right) >>> 1;
                int majorLeft = majorityElement(nums,left,mid);
                int majorRight = majorityElement(nums,mid+1,right);
                
                if (majorLeft == majorRight){
                    return majorLeft;
                }
                int countLeft = countInRange(nums, majorLeft, left, right);
                int countRight = countInRange(nums,majorRight,left,right);
                return countLeft<countRight ? majorRight:majorLeft;
            }
            private int countInRange(int[] nums, int num, int lo, int hi) {
                int count = 0;
                for (int i = lo; i <= hi; i++) {
                    if (nums[i] == num) {
                        count++;
                    }
                }
                return count;
            }
        ```
    #### 查找數組中的第K大
    
    // 使用大顶堆来进行 查找 第 K 大,优先队列默认是从大到小
    
    ``
     // 使用大顶堆进行查找
        public int findKthLargest(int[] nums, int k) {
            PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
            for (int i=0;i<nums.length;i++){
                queue.offer(nums[i]);
                if (queue.size() > k){
                    queue.poll();
                }
            }
            return queue.peek();
        }
    
    

    // 使用快排的分治思想

      // 使用快排思想分治分治算法
        public int findKthLargest(int[] nums, int k){
            // 快排找寻的是下标 
            return findKthLargest(nums,0,nums.length-1,k-1);
        }
        
        public int findKthLargest(int[] nums,int left,int right,int k){
            // 初始中轴
            int privot = nums[0];
            // 保存初始边界
            int temp_left = left;
            int temp_right = right;
            while (left < right){
                while (left < right && nums[right] <= privot){
                    right --;
                }
                nums[left] = nums[right];
                while (left < right && nums[left] >= privot){
                    left ++;
                }
                nums[right] = nums[left];
            }
            if (left == k){
                return nums[left];
            }else if (left > k){
                return findKthLargest(nums,temp_left,left-1,k);
            }else {
                return findKthLargest(nums,left+1,temp_right,k);
            }
        }
    

    搜索二维矩阵2

    按层进行二分查询

    public boolean searchMatrix(int[][] matrix, int target) {
            if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
                return false;
            }
            int m = matrix.length;
            int n = matrix[0].length;
            for (int i=0;i<m;i++){
                if (target >= matrix[i][0] && target <= matrix[i][n-1]){
                    int index = Arrays.binarySearch(matrix[i],target);
                    if (index > 0){
                        return true;
                    }
                }
            }
            return false;
        }
    

    // 搜索二维矩阵问题注意左下的问题

    public boolean searchMatrix2(int[][] matrix, int target){
            if (matrix == null || matrix.length == 0 || matrix[0].length == 0){
                return false;
            }
            int m = matrix.length;
            int n = matrix.length;
            // 从左下开始寻找,大于向右 小于向左
            int i = m-1,j = 0;
            while (i >= 0 && j < n){
                int x = matrix[i][j];
                if (x == target){
                    return true;
                }else if (x < target){
                    j++;
                }else {
                    i--;
                }
            }
            return false;
        }
    

    // 分治思想查找矩阵

    思路是矩阵的最大值是在右下,矩阵的最小值是左上

     public boolean searchMatrix4(int[][] matrix, int target){
            this.matrix = matrix;
            this.target = target;
            if (matrix == null || matrix.length == 0 || matrix[0].length == 0){
                return false;
            }
            return searchRec(0,0,matrix.length-1,matrix[0].length -1);
        }
        
        public boolean searchRec2(int up,int left,int down,int right){
            if (up < down || left > right){
                return false;
            }else if (target < matrix[up][left] || target > matrix[down][right]){
                return false;
            }
            
            int mid = left + (right - left)/2;
            int p = up;
            while (p <= down && matrix[p][mid] <= target){
                if (matrix[p][mid] == target){
                    return true;
                }
                p ++;
            }
            return searchRec(p,left,down,mid-1) || searchRec(up,mid+1,p-1,right);
        }
    

    为运算符设计优先级

    思路,是如果没有运算符就直接返回结果集,遍历找到运算符,将运算符分为左右两个部分进行组合

      // 分治的办法给表达式设计优先级
        public List<Integer> diffWaysToCompute(String input){
            return partition(input);
        }
        
        public List<Integer> partition(String input){
            List<Integer> result = new ArrayList<>();
            if (!input.contains("+") && !input.contains("-") && !input.contains("*")){
                result.add(Integer.parseInt(input));
                return result;
            }
            for (int i=0;i<input.length();i++){
                if (input.charAt(i) == "+" || input.charAt(i) == "-" || input.charAt(i) == "*"){
                    for (Integer left:partition(input.substring(0,i))){
                        for (Integer right:partition(input.substring(i+1))){
                            if (input.charAt(i) == "+"){
                                result.add(left + right);
                            }else if ((input.charAt(i) == "-")){
                                result.add(left - right);
                            }else {
                                result.add(left*right);
                            }
                        }
                    }
                }
            }
            return result;
        }
    

    给表达式添加运算符

    // 主要思路是回溯时采用三种符号进行回溯添加

       public List<String> addOperators(String num, int target) {
            List<String> result = new ArrayList<>();
            addOperators(num,target,0L,0L,"",result);
            return result;
        }
    
        public void addOperators(String num, int target,Long lastOpera, Long curNum, String tempRes, List<String> result){
            if (num.length() == 0 && curNum == target){
                result.add(tempRes);
                return;
            }
            // 回溯添加运算符
            for (int i=1;i<=num.length();i++){
                // 分割当前数和下一个数
                  // 分割当前数和下一个数
                // 这里主要采用分治分割 0i 作为前一个数, i ~ 最后作为后一个数
                String cur = num.substring(0,i);
                // 进行剪枝
                if (cur.length() > 1 && cur.charAt(0) == '0'){
                    return;
                }
                String next = num.substring(i);
                // 已经有第一个数
                if (tempRes.length() > 0){
                    // 尝试加
                    addOperators(next, target, Long.parseLong(cur),curNum + Long.parseLong(cur),tempRes + "+" + cur,result);
                    // 尝试减
                    addOperators(next,target,-Long.parseLong(cur),curNum - Long.parseLong(cur),tempRes + "-" + cur,result);
    
                    addOperators(next,target,lastOpera * Long.parseLong(cur),(curNum - lastOpera) + lastOpera * Long.parseLong(cur),tempRes + "*" + cur,result);
                }else {
                    addOperators(next,target,Long.parseLong(cur),Long.parseLong(cur),cur,result);
                }
            }
        }
    

    计算右侧小于当前元素个数

    // 使用的是 倒叙依次插入,然后使用 二分查找 返回具体的插入位置

    public List<Integer> countSmaller(int[] nums) {
            List<Integer> list = new ArrayList<>();
            if (nums == null || nums.length == 0){
                return list;
            }
            int n = nums.length;
            int[] counts = new int[n];
            List<Integer> temp = new ArrayList<>();
            for (int i=n-1;i>=0;i--){
                int index = binarySearch(temp, nums[i]);
                if (index < 0){
                    index = -(index+1);
                }
                System.out.println(index);
                temp.add(index,nums[i]);
                counts[i] = index;
            }
            for (int i=0;i<n;i++){
                list.add(counts[i]);
            }
            return list;
        }
    
        public int binarySearch(List<Integer> list, int target){
            int left = 0; int right = list.size();
            while (left < right){
                int mid = left + (right - left)/2;
                if (list.get(mid) >= target){
                    right = mid;
                }else {
                    left = mid + 1;
                }
            }
            return left;
        }
    

    // 使用 二茬搜索数计算右侧小于当前元素个数

    向左插只统计向左插入的个数,向右插开始统计插入的位置,并使用全局变量返回

      class BSTNode{
            int val;
            int count;
            BSTNode left;
            BSTNode right;
            public BSTNode(int x){
                this.val = x;
                this.count = 0;
            }
        }
        int count_small;
        public BSTNode BST_insert(BSTNode root, BSTNode node){
            // 往左插入代表,右边的数为0 ,但是其自身的数会 加
            if (root.val >= node.val){
                // 向左插入
                root.count ++;
                if (root.left != null){
                    root.left = BST_insert(root.left,node);
                }else {
                    // 左子树为 null
                    root.left = node;
                }
            }else {
                this.count_small += root.count + 1;
                if (root.right != null){
                    root.right = BST_insert(root.right,node);
                }else {
                    root.right = node;
                }
            }
            return root;
        }
    
        public List<Integer> countSmaller2(int[] nums){
            List<Integer> result = new ArrayList<>();
            if (nums == null || nums.length == 0){
                return result;
            }
            int n = nums.length;
            BSTNode node = new BSTNode(nums[n-1]);
            result.add(0,0);
            for (int i=1;i<n;i++){
                count_small = 0;
                node = BST_insert(node, new BSTNode(nums[n - i - 1]));
                result.add(0,count_small);
            }
            return result;
        }
    
      private void mergeSort(int[] nums, int left, int right, int[] ind, Integer[] ans) {
            if (left == right) {
                return;
            }
            int mid = (left + right) / 2;
            mergeSort(nums, left, mid, ind, ans);
            mergeSort(nums, mid+1, right, ind, ans);
            merge(nums, left, mid, right, ind, ans);
        }
    
        private void merge(int[] nums, int left, int mid, int right, int[] ind, Integer[] ans) {
            int[] larr = Arrays.copyOfRange(nums, left, mid+1), rarr = Arrays.copyOfRange(nums, mid+1, right+1), preind = Arrays.copyOfRange(ind, left, right+1);
            int i = 0, j = 0, k = left, cnt = 0, nl = larr.length, nr = rarr.length;
            while (i < nl && j < nr) {
                while (i < nl && j < nr && larr[i] <= rarr[j]) {
                    ans[preind[i]] += cnt;
                    ind[k] = preind[i];
                    nums[k++] = larr[i++];
                }
                while (i < nl && j < nr && rarr[j] < larr[i]) {
                    ind[k] = preind[j + nl];
                    nums[k++] = rarr[j++];
                    ++cnt;
                }
            }
            while (i < nl) {
                ans[preind[i]] += cnt;
                ind[k] = preind[i];
                nums[k++] = larr[i++];
            }
            while (j < nr) {
                ind[k] = preind[j + nl];
                nums[k++] = rarr[j++];
            }
        }
    
        public List<Integer> countSmaller4(int[] nums) {
            int n = nums.length;
            if (n == 0) {
                return Collections.emptyList();
            }
            Integer[] ans = new Integer[n];
            Arrays.fill(ans, 0);
            int[] ind = new int[n];
            for (int i=0; i<n; ++i) {
                ind[i] = i;
            }
            mergeSort(nums, 0, n-1, ind, ans);
            return new ArrayList<Integer>(Arrays.asList(ans));
        }
    

    // 使用归并分治进行统计计算右侧小于当前元素的个数

    思路是,当左边进行归并放入时,这是统计右边放入的个数,就是右边放入个数

    1. 创建临时数组,创建索引数组,创建统计数组,初始化索引数组
    2. 归并分割,l == r 进行回溯,分为两部分 l mid,mid +1 r 治,合并统计
    3. 复制索引数组,然后对索引数组进行排序,使用两个指针,指向 前半部分首位
      和后半部分首位,在归并左部数字时,右部已经归并的就是在右边的统计量
      当然在统计是采用 += 进行计算
    public List<Integer> countSmaller3(int[] nums) {
            List<Integer> res = new ArrayList<>();
            int len = nums.length;
            if (len == 0) {
                return res;
            }
            temp = new int[len];
            counter = new int[len];
            indexes = new int[len];
            // 保存一个索引数组,初始化索引数组
            for (int i = 0; i < len; i++) {
                indexes[i] = i;
            }
            mergeAndCountSmaller(nums, 0, len - 1);
            for (int i = 0; i < len; i++) {
                res.add(counter[i]);
            }
            return res;
        }
        
         private void mergeAndCountSmaller(int[] nums, int l, int r) {
            if (l == r) {
                // 数组只有一个元素的时候,没有比较,不统计
                return;
            }
            int mid = l + (r - l) / 2;
            mergeAndCountSmaller(nums, l, mid);
            mergeAndCountSmaller(nums, mid + 1, r);
            // 归并排序的优化,同样适用于该问题
            // 如果索引数组有序,就没有必要再继续计算了
            if (nums[indexes[mid]] > nums[indexes[mid + 1]]) {
                mergeOfTwoSortedArrAndCountSmaller(nums, l, mid, r);
            }
        }
    
        /**
         * [l, mid] 是排好序的
         * [mid + 1, r] 是排好序的
         *
         * @param nums
         * @param l
         * @param mid
         * @param r
         */
        private void mergeOfTwoSortedArrAndCountSmaller(int[] nums, int l, int mid, int r) {
            // 3,4  1,2
            for (int i = l; i <= r; i++) {
                temp[i] = indexes[i];
            }
            // 前半部数组第一个元素
            int i = l;
            // 后半数组第一个元素
            int j = mid + 1;
            // 遍历整个数组
            for (int k = l; k <= r; k++) {
                // 左指针超过mid 但还没结束,说明后半部分还有,将j 赋值k j++
                if (i > mid) {
                    indexes[k] = temp[j];
                    j++;
                    // j 大于 r 右半部分已经遍历完,但还没结束,说明左半部分还没遍历完,i赋值k i++; 当赋值左半部分要统计,
                    // 直接的右半部分,因为右边都已经出去,那么统计数, 整个右半部分
                } else if (j > r) {
                    indexes[k] = temp[i];
                    i++;
                    // 此时 j 用完了,[7,8,9 | 1,2,3]
                    // 之前的数就和后面的区间长度构成逆序
                    counter[indexes[k]] += (r - mid);
                    // 都没都终端 如果左 小于右 ,统计 右边已经出的
                } else if (nums[temp[i]] <= nums[temp[j]]) {
                    indexes[k] = temp[i];
                    i++;
                    // 此时 [4,5, 6   | 1,2,3 10 12 13]
                    //           mid          j
                    counter[indexes[k]] += (j - mid - 1);
                    // 右边进行交换
                } else {
                    // nums[indexes[i]] > nums[indexes[j]] 构成逆序
                    indexes[k] = temp[j];
                    j++;
                }
            }
        }
    

    相关文章

      网友评论

        本文标题:分治算法

        本文链接:https://www.haomeiwen.com/subject/hojowctx.html