分治算法
分治算法的核心思想就是分而治之。将原问题划分成n个小规模的问题,并且结构与原问题相似,递归的去解决这些子问题然后在合并其结果,就得到了原问题的解。
分治算法是一种处理问题的思想,递归是一种编程技巧。
满足分治的条件
- 原问题与分解成的小问题具有相同的模式;
- 原问题分解成的子问题可以独立求解,子问题之间没有相关性,这点是分治算法跟动态规划的明显区别;
- 具有分解终止条件,也就是说,当问题足够小的时候可以直接求解;
- 子问题和并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。
分治算法题
1.归并排序
给你一个整数数组 nums,请你将该数组升序排列。
class Solution {
int[] aux;
public int[] sortArray(int[] nums) {
aux = new int[nums.length];
guibing(nums,0,nums.length-1);
return nums;
}
public void guibing(int[] nums,int l,int r){
if(l>=r){
return;
}
int mid = l+(r-l)/2;
guibing(nums,l,mid);
guibing(nums,mid+1,r);
combine(nums,l,mid,r);
}
//合并有序数组
public void combine(int[] nums,int l,int mid,int r){
int i = l,j=mid+1;
for(int m = l;m<=r;m++){
aux[m]=nums[m];
}
for(int m = l;m<=r;m++){
if(i>mid){
nums[m]=aux[j++];
}else if(j>r){
nums[m]=aux[i++];
}else if(aux[i]<aux[j]){
nums[m]=aux[i++];
}else{
nums[m]=aux[j++];
}
}
}
}
2. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
思路:利用归并排序的思想,逆序度等于左侧的逆序度+右侧的逆序度+两者变量的逆序度;在merge阶段进行判断,因为待merge的两个数组都是分别有序的,所以只用求两者之间的逆序度就可以。
class Solution {
int[] aux;
int res =0;
public int reversePairs(int[] nums) {
int n = nums.length;
aux = new int[n];
combine_sort(nums,0,n-1);
return res;
}
public void combine_sort(int[]nums,int l,int r){
if(l>=r){
return;
}
int mid = l+(r-l)/2;
combine_sort(nums,l,mid);
combine_sort(nums,mid+1,r);
merge(nums,l,mid,r);
}
public void merge(int[] nums,int l,int mid,int r){
int i =l,j = mid+1;
for(int m = l;m<=r;m++){
aux[m]=nums[m];
}
for(int m = l;m<=r;m++){
if(i>mid){
nums[m]=aux[j++];
}else if(j>r){
nums[m]=aux[i++];
}else if(aux[i]>aux[j]){
res += mid-i+1;//因为左右两侧都是有序的,左侧i处的值都大于右侧j的值了,所以左侧i后的值都大于j处的值。
nums[m]=aux[j++];
} else{
nums[m]=aux[i++];
}
}
}
}
3. 将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
思路:因为是有序数组,所以符合树的中序遍历,但是中序遍历也不能确定一棵树,所以此题有多种答案,但是有一个条件,高度平衡,所有左右子树的高度差不会超过1
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums,0,nums.length-1);
}
public TreeNode helper(int[] nums,int l ,int r){
if(l>r){
return null;
}
int mid = (l+r)/2;
TreeNode node = new TreeNode(nums[mid]);
node.left = helper(nums,l,mid-1);
node.right = helper(nums,mid+1,r);
return node;
}
}
4.最小k个数
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
思路:利用快排思想
class Solution {
public int[] smallestK(int[] arr, int k) {
//思路一:利用数组排序
// Arrays.sort(arr);
// int[] nums = new int[k];
// for(int i =0;i<k;i++){
// nums[i]=arr[i];
// }
// return nums;
//思路二,利用快速排序算法
int[] nums = new int[k];
quick_sort(arr,0,arr.length-1,k);
for(int i =0;i<k;i++){
nums[i]=arr[i];
}
return nums;
}
public void quick_sort(int[] nums,int l,int r,int k){
if(l>=r){
return;
}
int partition = quicks(nums,l,r);
if(partition==k){
return;
}else if(partition<k){
quick_sort(nums,partition+1,r,k);
}else{
quick_sort(nums,l,partition-1,k);
}
}
public int quicks(int[] nums,int lo,int hi){
int temp = nums[lo];
int l =lo,r=hi+1;
while(true){
while(l<hi && nums[++l]<temp){
if(l==hi){
break;
}
}
while(r>lo && nums[--r]>temp){
if(r==lo){
break;
}
}
if(l>=r){
break;
}
exch(nums,l,r);
}
exch(nums,lo,r);
return r;
}
public void exch(int[] nums,int l,int r){
int temp = nums[l];
nums[l]=nums[r];
nums[r]=temp;
}
}
5.最大二叉树
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
二叉树的根是数组 nums 中的最大元素。
左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
返回有给定数组 nums 构建的 最大二叉树 。
思路:利用递归构建二叉树
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return buildTree(nums,0,nums.length-1);
}
public TreeNode buildTree(int[] nums,int l,int r){
if(l>r){//有了这个判断了,在searchMax的函数中就不用再添加判断了,就能保证l是小于等于r的
return null;
}
int rootVlue = searchMax(nums,l,r);
// if(rootVlue !=-1){//参见上面的注释,此处就作废了
TreeNode root = new TreeNode(nums[rootVlue]);
root.left = buildTree(nums,l,rootVlue-1);
root.right = buildTree(nums,rootVlue+1,r);
return root;
// }else{
// return null;
// }
}
public int searchMax(int[]nums,int l,int r){//返回最大值下标
// if(l>r){
// return -1;
// }
int maxValue = l;
for(int i = l;i<=r;i++){
if(nums[i]>nums[maxValue]){
maxValue = i;
}
}
return maxValue;
}
}
6.有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
思路:分治思想,递归构建,由于限制了高度,所以,每次就选中间位置的作为根节点,然后在构建左右子树
class Solution {
public TreeNode sortedListToBST(ListNode head) {
return buildTree(head,null);
}
public TreeNode buildTree(ListNode left,ListNode right){
if(left==right){
return null;
}
ListNode node = searchMax(left,right);
TreeNode root = new TreeNode(node.val);
root.left = buildTree(left,node);
root.right = buildTree(node.next,right);
return root;
}
public ListNode searchMax(ListNode left,ListNode right){//给定一个链表,返回中间节点的node,以及将其分成两部分,
ListNode low = left,fast = left;
while(low!=right && fast!=right && fast.next!=right){
low = low.next;
fast = fast.next.next;
}
return low;
}
}
7.从中序和后序遍历构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
思路:中序遍历:左、根、右;后序遍历:左、右、根;
所以我们可以通过后续遍历的最后的一个值作为根节点来构建二叉树,
但是需要先构建右子树,因为后序遍历的数组中,往前推,先是右子树的根,再是左子树的根;
class Solution {
int rootPos = 0;//存放根节点的位置
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();//存放中序遍历的值和所对应的位置
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i =0;i<inorder.length;i++){
map.put(inorder[i],i);
}
rootPos = postorder.length-1;//在postorder中根节点的位置是最后一个位置,前一个是右子树的根节点,所以后续构建的时候要先构建右子树,在构建左子树【如果不想这样的化,可以根据中序遍历的子树的结点,在后续遍历中找到最右的那一个就是该子树的根节点===猜的,还没有验证】
return build(postorder,0,inorder.length-1);
}
//构建二叉树
public TreeNode build(int[] postorder,int l,int r){
if(l>r){//递归终止条件,不是l>=r
return null;
}
int rootValue = postorder[rootPos];
TreeNode root = new TreeNode(rootValue);
rootPos--;
root.right = build(postorder,map.get(rootValue)+1,r);
root.left = build(postorder,l,map.get(rootValue)-1);
return root;
}
}