divide and conquer
文章目录
- divide and conquer
- 基本步骤
- 使用场景
- Leetcode题
- [148. 排序链表](https://leetcode-cn.com/problems/sort-list/)
- [23. 合并K个升序链表](https://leetcode-cn.com/problems/merge-k-sorted-lists/)
- [395. 至少有K个重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters/)
- [剑指 Offer 51. 数组中的逆序对](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/)
- [315. 计算右侧小于当前元素的个数](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/)
分治,字面意思是“分而治之”,就是把一个复杂的1问题分成两个或多个相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单地直接求解,原问题的解即子问题的解的合并,这个思想是很多高效算法的基础,例如排序算法(快速排序,归并排序),傅里叶变换(快速傅里叶变换)等。
基本步骤
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 合并:将各个子问题的解合并为原问题的解
使用场景
- 该问题的规模缩小到一定的程度就可以容易的解决。
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
- 利用该问题分解出的子问题的解可以合并为该问题的解。没有这个条件,就不能分治了
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。如果不独立,则用动态规划比较好
Leetcode题
148. 排序链表
func sortList(head *ListNode) *ListNode {
return mergeSort(head)
}
func mergeSort(root *ListNode) (head *ListNode) {
if root == nil {
return nil
} else if root.Next == nil {
return root
}
slow, fast := root, root.Next
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
end := slow
end.Next = nil // 一定要做,需要断开这个,不然死循环(爆栈)
slow = slow.Next
left := mergeSort(root)
right := mergeSort(slow)
return merge(left, right)
}
func merge(left, right *ListNode) *ListNode {
pre := &ListNode{}
cur := pre
for left != nil && right != nil {
if left.Val < right.Val {
cur.Next = left
cur = left
left = left.Next
} else {
cur.Next = right
cur = right
right = right.Next
}
}
if left != nil {
cur.Next = left
}
if right != nil {
cur.Next = right
}
return pre.Next
}
// 不使用递归的做法,空间复杂度O(1)
func mergeSortLoacl(head *ListNode) *ListNode {
h, length, interval := head, 0, 1
for h != nil {
length++
h = h.Next
}
res := &ListNode{}
res.Next = head
pre := res
for interval < length { // 结束条件, interval为每次归并的子序列长度1,2,4...
pre, h = res, res.Next // 每次都从头开始
for h != nil { // 是否遍历完链表
h1, i := h, interval
for h != nil && i > 0 { // 找h1部分
h = h.Next
i--
}
if i > 0 { // h1长度都不够interval了,直接跳出了(h2不可能存在,都不用合并了)
break
}
h2, j := h, interval
for h != nil && j > 0 { // 找h2部分
h = h.Next
j--
}
c1, c2 := interval, interval - j // c2 大小需要减去j是因为h2有可能不够interval的长度
for c1 > 0 && c2 > 0 { // 合并h1 h2,pre就是那个辅助节点
if h1.Val < h2.Val {
pre.Next, h1 = h1, h1.Next
c1--
} else {
pre.Next, h2 = h2, h2.Next
c2--
}
pre = pre.Next
}
if c1 > 0 {
pre.Next = h1
} else {
pre.Next = h2
}
// pre需要指向最后的h,需要拼接上才行,要不然会断开,因为不一定h就是指向最后
// 比如说合并完第一个和第二个,第三个和第四个需要连接上,也就是pre.Next需要指向第三个
// 开头,也就是h指向的位置
for c1 > 0 || c2 > 0 {
pre = pre.Next
c1, c2 = c1 - 1, c2 - 1
}
pre.Next = h
}
interval *= 2
}
return res.Next
}
23. 合并K个升序链表
类似的想法,不过这里用递归就好了,用Local也可以,不过不需要pre.Next = h
,需要变一下才行。
// 这里就是递归形式而已,比较方便
func mergeKLists(lists []*ListNode) *ListNode {
length := len(lists)
if length == 0 {
return nil
}
return merge(lists, 0, length - 1)
}
func merge(lists []*ListNode, left, right int) *ListNode {
if left == right {
return lists[left]
}
mid := (right + left) / 2
h1 := merge(lists, left, mid)
h2 := merge(lists, mid + 1, right)
return mergeTwo(h1, h2)
}
func mergeTwo(root1, root2 *ListNode) *ListNode {
pre := &ListNode{}
cur := pre
for root1 != nil && root2 != nil {
if root1.Val < root2.Val {
cur.Next = root1
root1 = root1.Next
} else {
cur.Next = root2
root2 = root2.Next
}
cur = cur.Next
}
if root1 != nil {
cur.Next = root1
}
if root2 != nil {
cur.Next = root2
}
return pre.Next
}
395. 至少有K个重复字符的最长子串
func longestSubstring(s string, k int) int {
if len(s) < k {
return 0
}
if k < 2 {
return len(s)
}
arr:= make([]int, len(s))
for i := 0; i < len(s); i++ { // 转化为数组会快一下
arr[i] = int(s[i] - 'a')
}
return helper(arr, 0, len(s) - 1, k)
}
func helper(arr []int, left, right, k int) int{
if right - left + 1 < k {
return 0
}
cout := make([]int, 26)
for i := left; i <= right; i++ { // 统计数量
cout[arr[i]]++
}
for right - left + 1 >= k && cout[arr[left]] < k { // 刨掉左边少于k的字符串
left++
}
for right - left + 1 >= k && cout[arr[right]] < k { // 刨掉右边少于k的字符串
right--
}
for i := left; i <= right; i++ {
if cout[arr[i]] < k { // 如果有一个不对劲就以这个为分界线划分数组,递归寻找最长的字符串
return max(helper(arr, left, i - 1, k), helper(arr, i + 1, right, k))
}
}
return right - left + 1 // 如果都大于k,那就直接返回这个长度罗
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
剑指 Offer 51. 数组中的逆序对
315. 计算右侧小于当前元素的个数
这两个题都是类似的,都是在分治归并的过程中找到问题的解
// Offer 51
func reversePairs(nums []int) int {
res := 0
var helper func(left, right int)
var merge func(left, mid, right int)
helper = func(left, right int) {
if left >= right {
return
}
mid := (right - left) / 2 + left
helper(left, mid)
helper(mid + 1, right)
merge(left, mid, right)
}
merge = func(left, mid, right int) {
if left == right {
return
}
arr := make([]int, right - left + 1)
i, j, k := left, mid + 1, 0
for i <= mid && j <= right {
if nums[j] < nums[i] {
res += mid - i + 1
arr[k] = nums[j]
j++
} else {
arr[k] = nums[i]
i++
}
k++
}
for i <= mid {
arr[k] = nums[i]
i++
k++
}
for j <= right {
arr[k] = nums[j]
j++
k++
}
for i = 0; i < k; i++ {
nums[left + i] = arr[i]
}
}
helper(0, len(nums) - 1)
return res
}
func countSmaller(nums []int) []int {
res := make([]int, 0)
if len(nums) == 0 {
return res
}
tmp := append([]int{}, nums...)
sort.Ints(tmp)
for _, v := range nums {
index := findIndex(tmp, v)
if tmp[index] < v {
res = append(res, index + 1)
if index + 2 < len(tmp) {
tmp = append(tmp[:index + 1], tmp[index + 2:]...)
} else {
tmp = tmp[:index + 1]
}
} else {
res = append(res, 0)
if len(tmp) > 1 {
tmp = tmp[1:]
}
}
}
return res
}
func findIndex(nums []int, target int) int {
left := 0
right := len(nums) - 1
for ; left < right; {
mid := left + (right - left + 1) / 2
if nums[mid] >= target {
right = mid - 1
} else {
left = mid
}
}
return left
}