数据结构与算法 - 分治

news2024/9/23 7:28:53

一、概述

分治思想

  • 将大问题划分为两个到多个子问题
  • 子问题可以继续拆分成更小的子问题,直到能简单求解
  • 如有必要,将子问题的解进行合并,得到原始问题的解

1. 二分查找

public static int binarySearch(int[] a, int target) {
    return recursion(a, target, 0, a.length - 1);
}

public static int recursion(int[] a, int target, int i, int j) {
    if (i > j) {
        return -1;
    }
    int m = (i + j) >>> 1;
    if (target < a[m]) {
        return recursion(a, target, i, m - 1);
    } else if (a[m] < target) {
        return recursion(a, target, m + 1, j);
    } else {
        return m;
    }
}

减而治之,每次搜索范围内元素减少一半。

2. 快速排序

public static void sort(int[] a) {
    quick(a, 0, a.length - 1);
}

private static void quick(int[] a, int left, int right) {
    if (left >= right) {
        return;
    }
    int p = partition(a, left, right);
    quick(a, left, p - 1);
    quick(a, p + 1, right);
}

分而治之,这次分区基准点,在划分之后两个区域分别进行下次分区。


3. 归并排序

public static void sort(int[] a1) {
    int[] a2 = new int[a1.length];
    split(a1, 0, a1.length - 1, a2);
}

private static void split(int[] a1, int left, int right, int[] a2) {
    int[] array = Arrays.copyOfRange(a1, left, right + 1);
    // 2. 治
    if (left == right) {
        return;
    }
    // 1. 分
    int m = (left + right) >>> 1;
    split(a1, left, m, a2);                 
    split(a1, m + 1, right, a2);       
    // 3. 合
    merge(a1, left, m, m + 1, right, a2);
    System.arraycopy(a2, left, a1, left, right - left + 1);
}

分而治之,分到区间内只有一个元素,合并区间。

4. 合并K个排序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2:

输入:lists = []
输出:[]

示例 3:

输入:lists = [[]]
输出:[]

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] 按 升序 排列
  • lists[i].length 的总和不超过 10^4
/**
 * 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 mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode s = new ListNode(-1, null);
        ListNode p = s;

        while (list1 != null && list2 != null) {
            // 谁小把谁链给p,p和小的都向后平移一位
            if (list1.val < list2.val) {
                p.next = list1;
                list1 = list1.next;
            } else {
                p.next = list2;
                list2 = list2.next;
            }
            p = p.next;
        }

        // 处理剩余节点
        if (list1 != null) {
            p.next = list1;
        }

        if (list2 != null) {
            p.next = list2;
        }

        return s.next;
    }

    public ListNode split(ListNode[] lists, int i, int j) {
        if (j == i) {
            return lists[i];
        }

        int m = (i + j) >>> 1;
        return mergeTwoLists(split(lists, i, m), split(lists, m + 1, j));
    }

    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) {
            return null;
        }

        return split(lists, 0, lists.length - 1);
    }
}

分而治之,分到区间内只有一个链表,合并区间

5. 对比动态规划

  • 都需要拆分子问题
  • 动态规划的子问题有重叠,因此需要记录之前子问题解,避免重复运算
  • 分而治之的子问题无重叠

二、快速选择算法

快速选择(Quickselect)算法是一种用于从未排序的列表中选择第 k 小(或第 k 大)元素的方法,它基于快速排序(Quicksort)算法的原理。其平均时间复杂度为 O(n),最坏情况下为 O(n^2),但在大多数情况下运行得很快。

package com.itheima.algorithms.divideandconquer;

import java.util.concurrent.ThreadLocalRandom;

/**
 * 快速选择算法 - 分而治之
 */
public class QuickSelect {

    /**
     * 求排在第i名的元素,i从0开始,由小到大排
     * 6 5 1 2 4
     */

    public static int quick(int[] array, int left, int right, int i) {
        /*
            6   5   1   2   [4]

                    2
            1   2   4   6   5

            1   2   4   6   [5]
                        3
            1   2   4   5   6
         */

        // 基准点元素索引值
        int p = partition(array, left, right);
        if(p == i) {
            return array[p];
        }
        if(i < p) {
            // 到左边找
            return quick(array, left, p - 1, i);
        } else {
            // 到右边找
            return quick(array, p + 1, right, i);
        }
    }

    private static int partition(int[] a, int left, int right) {
        int idx = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
        swap(a, idx, left);
        int pivot = a[left];
        int i = left + 1, j = right;

        while (i <= j) {
            // 2. i从左向右找大的
            while (i <= j && a[i] < pivot) {
                i++;
            }
            // 1. j从右向左找小(等)的
            while (i <= j && a[j] > pivot) {
                j--;
            }

            if(i <= j) {
                swap(a, i, j);
                i++;
                j--;
            }
        }
        swap(a, j, left);

        return j;
    }

    private static void swap(int[] a, int i, int j) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }


    public static void main(String[] args) {
        int [] array = {6, 5, 1, 2, 4};
        System.out.println(quick(array, 0, array.length - 1, 0));  // 1
        System.out.println(quick(array, 0, array.length - 1, 1));  // 2
        System.out.println(quick(array, 0, array.length - 1, 2));  // 4
        System.out.println(quick(array, 0, array.length - 1, 3));  // 3
        System.out.println(quick(array, 0, array.length - 1, 4));  // 6
    }
}

1. 数组中第k个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

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

示例 2:

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

提示:

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

解法一:执行耗时6ms

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return quick(nums, 0, nums.length - 1, nums.length - k);
    }

    public static int quick(int[] array, int left, int right, int i) {

        // 基准点元素索引值
        int p = partition(array, left, right);
        if (p == i) {
            return array[p];
        }
        if (i < p) {
            // 到左边找
            return quick(array, left, p - 1, i);
        } else {
            // 到右边找
            return quick(array, p + 1, right, i);
        }
    }

    private static int partition(int[] a, int left, int right) {
        int idx = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
        swap(a, idx, left);
        int pivot = a[left];
        int i = left + 1, j = right;

        while (i <= j) {
            // 2. i从左向右找大的
            while (i <= j && a[i] < pivot) {
                i++;
            }
            // 1. j从右向左找小(等)的
            while (i <= j && a[j] > pivot) {
                j--;
            }
            
            if(i <= j) {
                swap(a, i, j);
                i++;
                j--;
            }
        }
        swap(a, j, left);

        return j;
    }

    private static void swap(int[] a, int i, int j) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}

2. 数组中位数

package com.itheima.algorithms.divideandconquer;

import java.util.concurrent.ThreadLocalRandom;

class Solution {
    /*
        偶数个
            3   1   5   4
        奇数个
            4   5   1
            4   5   1   6   3
     */
    public static double findMedian(int[] nums) {
        if (nums.length % 2 != 0) {
            // 奇数个
            return findIndex(nums, 0, nums.length - 1, nums.length / 2);
        } else {
            int a = findIndex(nums, 0, nums.length - 1, nums.length / 2);
            int b = findIndex(nums, 0, nums.length - 1, nums.length / 2 - 1);

            return (a + b) / 2.0;
        }
    }


    public static int findIndex(int[] array, int left, int right, int i) {
        /*
            6   5   1   2   [4]

                    2
            1   2   4   6   5

            1   2   4   6   [5]
                        3
            1   2   4   5   6
         */

        // 基准点元素索引值
        int p = partition(array, left, right);
        if (p == i) {
            return array[p];
        }
        if (i < p) {
            // 到左边找
            return findIndex(array, left, p - 1, i);
        } else {
            // 到右边找
            return findIndex(array, p + 1, right, i);
        }
    }

    private static int partition(int[] a, int left, int right) {
        int idx = ThreadLocalRandom.current().nextInt(right - left + 1) + left;
        swap(a, idx, left);
        int pivot = a[left];
        int i = left + 1, j = right;

        while (i <= j) {
            // 2. i从左向右找大的
            while (i <= j && a[i] < pivot) {
                i++;
            }
            // 1. j从右向左找小(等)的
            while (i <= j && a[j] > pivot) {
                j--;
            }

            if (i <= j) {
                swap(a, i, j);
                i++;
                j--;
            }
        }
        swap(a, j, left);

        return j;
    }

    private static void swap(int[] a, int i, int j) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }


    public static void main(String[] args) {
        int[] nums = {1, 4, 5, 2, 7};
        System.out.println(findMedian(nums));
        int[] nums2 = {3, 2, 8, 5, 7, 10};
        System.out.println(findMedian(nums2));
    }
}

三、快速幂

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

提示:

  • -100.0 < x < 100.0
  • -2^31 <= n <= 2^31-1
  • n 是一个整数
  • 要么 x 不为零,要么 n > 0 。
  • -10^4 <= xn <= 10^4

解法一:

class Solution {
    public static double myPow(double x, int n) {
        if(n == 0) {
            return 1.0;
        }
        if(n == 1) {
            return x;
        }
        double y = myPow(x, n / 2);

        /*
            1   001
            3   011
            5   101
            7   111
                001 &
                ---
                001

            2   010
            4   100
            6   110
            8  1000
            奇数的二进制末位都是1,偶数的二进制末位都是0
            与001进行按位与运算,如果结果为0,则为偶数
         */
        if((n & 1) == 0) {
            // 偶数次幂
            return  y * y;
        } else if(n > 0){
            // 奇数
            return x * y * y;
        } else {
            // n为负数
            return y * y / x;
        }
    }
}

四、平方根整数部分

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

  • 0 <= x <= 2^31 - 1

解法一:

class Solution {
    public int mySqrt(int x) {
        if(x == 1) {
            return 1;
        }
        
        int min = 0;
        int max = x;

        while(max - min > 1) {
            int mid = (max + min) >> 1;
            if(x / mid < mid) {
                // 平方根落在m的左侧,更新max
                max = mid;
            } else {
                // 平方根落在m的右侧,更新min
                min = mid;
            }
        }

        return min;
    }
}

五、至少k个重复字符的最长子串

给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。

如果不存在这样的子字符串,则返回 0。

示例 1:

输入:s = "aaabb", k = 3
输出:3
解释:最长子串为 "aaa" ,其中 'a' 重复了 3 次。

示例 2:

输入:s = "ababbc", k = 2
输出:5
解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。

提示:

  • 1 <= s.length <= 10^4
  • s 仅由小写英文字母组成
  • 1 <= k <= 10^5

解法一:分治

class Solution {
    public int longestSubstring(String s, int k) {
        return longestSubstringHelper(s, k);
    }

    private int longestSubstringHelper(String s, int k) {
        HashMap<Character, Integer> map = new HashMap<>();

        // 统计字符出现次数
        for (char ch : s.toCharArray()) {
            map.put(ch, map.getOrDefault(ch, 0) + 1);
        }

        // 检查是否所有字符都满足出现次数要求
        for (char ch : map.keySet()) {
            if (map.get(ch) < k) {
                // 如果某个字符出现次数小于 k,则分割字符串
                int maxLen = 0;
                for (String part : s.split(String.valueOf(ch))) {
                    maxLen = Math.max(maxLen, longestSubstringHelper(part, k));
                }
                return maxLen;
            }
        }

        // 所有字符出现次数都大于等于 k,返回当前字符串的长度
        return s.length();
    }
}

class Solution {
    public int longestSubstring(String s, int k) {
        if (s.length() < k) {
            return 0;
        }
        int[] counts = new int[26];

        char[] chars = s.toCharArray();
        for (char ch : chars) {
            counts[ch - 'a']++;
        }

        System.out.println(Arrays.toString(counts));
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            int count = counts[c - 'a'];
            if (count > 0 && count < k) {
                int j = i + 1;
                while (j < s.length() && counts[chars[j] - 'a'] < k) {
                    j++;
                }
                System.out.println(s.substring(0, i) + '\t' + s.substring(j));
                // 分治
                return Integer.max(longestSubstring(s.substring(0, i), k), longestSubstring(s.substring(j), k));
            }
        }

        return s.length();
    }
}

优化:

class Solution {
    public int longestSubstring(String s, int k) {
        // 基础判定,字符串长度小于k时返回0
        if (s.length() < k) {
            return 0;
        }

        // 统计每个字符出现的次数
        int[] counts = new int[26];
        for (char ch : s.toCharArray()) {
            counts[ch - 'a']++;
        }

        // 检查是否有字符出现次数小于k
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (counts[c - 'a'] < k) {
                // 统计到出现次数小于k的字符,进行分治
                int j = i + 1;
                while (j < s.length() && counts[s.charAt(j) - 'a'] < k) {
                    j++;
                }
                // 递归处理左右部分的子字符串
                return Math.max(longestSubstring(s.substring(0, i), k),
                        longestSubstring(s.substring(j), k));
            }
        }

        // 所有字符的出现次数都大于等于k,返回原字符串的长度
        return s.length();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2050799.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

遇到的几个iOS问题

1 unable to boot the simulator 跑模拟器的时候遇到这个报错&#xff0c; 解决方法 处理办法&#xff1a; 删除升级之前的模拟器缓存&#xff0c;重启模拟器。删除路径&#xff1a;~/Library/Developer/CoreSimulator/Cache 注意&#xff1a;后面可能还会复现这个报错&#x…

计算机毕业设计选题推荐-养老院管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

vue2项目支持@用户的文本域,使用at-textarea实现

vue2项目支持用户的文本域&#xff0c;使用at-textarea 第一步安装at-textarea npm install at-textarea第二步引入并注册 import AtTextarea from "at-textarea" components:{AtTextarea }第三步使用 <AtTextarea:AtList"AtList":AtConfig"{k…

CData Drivers for Cosmos DB Crack

CData Drivers for Cosmos DB Crack   Key Features of CData Drivers for Cosmos DB: Multi-API Support: Compatibility with various Cosmos DB APIs, such as SQL, MongoDB, Cassandra, Gremlin, and Table, allows for data model flexibility. SQL Query Execution: A…

Frida 的下载和安装

首先要安装好 python 环境 安装 frida 和 工具包 pip install frida frida-tools 查看版本&#xff1a; frida --version 16.4.8 然后到 github 上下载对应 server &#xff08; 和frida 的版本一致 16.4.8&#xff09; Releases frida/frida (github.com) 查看手机或…

计算机毕业设计选题推荐-高校实验室管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

CommunityToolkit.MVVM

前言 MVVM工具包&#xff0c;以前名为 Microsoft.Toolkit.Mvvm 由 Microsoft 维护和发布&#xff0c;是 .NET Foundation 的一部分。 支持&#xff1a;.NET Standard 2.0、 .NET Standard 2.1 和 .NET 6(UI Framework 不可知&#xff0c;基本使用没有问题/编译特性用不了) 注…

C语言-写一函数,实现两个字符串的比较。即自己写一个strcmp函数,函数原型为 int strcmp(char *pl,char *p2);

题目要求&#xff1a; 17.写一函数,实现两个字符串的比较。即自己写一个strcmp函数,函数原型为 int strcmp(char *pl,char *p2); 设p1指向字符串s1&#xff0c;p2指向字符串s2。要求当s1s2时,返回值为0;若s1≠s2,返回它们二者第1个不同字符的 ASCI1码差值(如"BOY"与…

微信小程序--28(npm包)

目录 一、小程序对npm的支持与限制&#xff0c; 二、什么是Vant Weapp 三、安装Vant组件库 &#xff08;一&#xff09;通过npm安装 &#xff08;二&#xff09;构建npm包 &#xff08;三&#xff09;修改 app.json 一、小程序对npm的支持与限制&#xff0c; 小程序已经支…

STM32标准库学习笔记-6.定时器-输入捕获

参考教程&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 定时器输入捕获 IC&#xff08;Input Capture&#xff09;输入捕获输入捕获模式下&#xff0c;当通道输入引脚出现指定电平跳变时&#xff0c;当前CNT的值将被锁存到CCR中&#xff0c;可用于测量PWM波形的频率…

一起看下halcon逻辑结构

halcon中结构包括顺序结构、分支结构、循环结构三种&#xff0c;不存在跳转的结构语句。 注意cntinue:结束本次循环,break跳出本次循环 选择结构&#xff1a; 1.if…end if 2.if…else…end if 3.if…elseif…elseif…end if 循环结构&#xff1a; 1.while()…endwhile …

<STC32G12K128入门第十二步>STC32G低功耗设计

前言 本文主要讲STC32G的低功耗设计,包括软件设计和硬件设计。其中有软件有一个问题当时困扰了我几个小时。都是精华 一、STC32G低功耗硬件设计 STC32G的硬件设计思路,最基本的就是需要考虑使用低功耗的硬件,比如ldo或者dc-dc需要考虑他的静态功耗,最好选择ua级别的。然…

【Hot100】LeetCode—206. 反转链表

目录 1- 思路递归法 2- 实现⭐206. 反转链表——题解思路 3- ACM 实现 原题连接&#xff1a;206. 反转链表 1- 思路 递归法 递归三部曲 ①终止条件&#xff1a;遇到 head null || head.nextnull 的时候②递归逻辑&#xff1a;定义 cur &#xff0c;cur 执行递归逻辑&#xff…

<数据集>商品条形码识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3748张 标注数量(xml文件个数)&#xff1a;3748 标注数量(txt文件个数)&#xff1a;3748 标注类别数&#xff1a;1 标注类别名称&#xff1a;[Barcode] 序号类别名称图片数框数1Barcode37484086 使用标注工具&am…

4. 寻找两个正序数组的中位数(困难)

4. 寻找两个正序数组的中位数 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;4. 寻找两个正序数组的中位数 2.详细题解 两个有序数组&#xff0c;寻找二者的中位数&#xff0c;最直观的方法是先归并这两个数组为一个有序数组&#x…

SQLite数据库的创建和升级

SQLite数据库的创建和升级 在刚开始接触Android的时候&#xff0c;我甚至都不敢相信&#xff0c;Android系统竟然是内置了数据库的&#xff01;好吧&#xff0c;是我太孤陋寡闻了。SQLite是一款轻量级的关系型数据库&#xff0c;它的运算速度非常快&#xff0c;占用资源很少&a…

开源通用验证码识别OCR —— DdddOcr 源码赏析(一)

文章目录 [toc] 前言DdddOcr环境准备安装DdddOcr使用示例 源码分析实例化DdddOcr实例化过程 分类识别分类识别过程 未完待续 前言 DdddOcr 源码赏析 DdddOcr DdddOcr是开源的通用验证码识别OCR 官方传送门 环境准备 安装DdddOcr pip install ddddocr使用示例 示例图片如…

Datawhale X 魔搭 AI夏令营第四期 魔搭-AIGC方向全过程笔记

task1: 传送门 task2&#xff1a; 传送门 task3: 传送门 目录 Task1 赛题内容 可图Kolors-LoRA风格故事挑战赛 baseline要点讲解(请配合Datawhale速通教程食用) Step1 设置算例及比赛账号的报名和授权 Step2 进行赛事报名并创建PAI实例 Step3 执行baseline Step4…

软件架构:架构模式、特征及实践指南-读书笔记(1)

第二章 架构思维 2.1 架构与设计 为了使架构落地&#xff0c;必须打破阻碍在架构师和开发人员之间的所有障碍&#xff0c;从而使架构师和开发团队之间形成双向的强关联。如图2-3所示&#xff0c;架构师和开发人员必须在同一个虚拟团队中才能使架构落地。该模型不仅促进了架构师…