剑指Offer刷题小结--五(25~30)

目录

  • 第一题:复杂链表的复制
  • 第二题:二叉搜索树与双向链表
  • 第三题:字符串的排列
  • 第四题:数组中出现次数超过一半的数字
  • 第五题:最小的K个数
  • 第六题:连续子数组的最大和

第一题:复杂链表的复制

题目链接
题目

在这里插入图片描述

解析

这个题目和LeetCode-138.Copy List with Random Pointer完全一样,那篇博客有详细的解释,不再赘述。
方法一: 使用HashMap保存

    public RandomListNode Clone(RandomListNode pHead){
        HashMap<RandomListNode,RandomListNode> map = new HashMap<>();
        RandomListNode cur = pHead;
        while(cur != null){
            map.put(cur,new RandomListNode(cur.label));
            cur = cur.next;
        }
        cur = pHead;
        while(cur != null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(pHead);
    }

方法二: 方法一的另一种写法

    public RandomListNode Clone(RandomListNode pHead){
        if(pHead == null)
            return null;
        HashMap<RandomListNode,RandomListNode>map = new HashMap<>();
        RandomListNode copyHead = new RandomListNode(pHead.label);
        map.put(pHead,copyHead);
        
        RandomListNode cur = pHead,copyCur = copyHead;
        
        while(cur != null){
            if(cur.next != null)
                copyCur.next = new RandomListNode(cur.next.label);
            map.put(cur.next,copyCur.next);
            cur = cur.next;
            copyCur = copyCur.next;
        }
        
        cur = pHead;
        while(cur != null){
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return copyHead;
    }

方法二的递归写法:

    public RandomListNode Clone(RandomListNode pHead){
        if(pHead == null)
            return null;
        HashMap<RandomListNode,RandomListNode>map = new HashMap<>();
        RandomListNode copyHead = process(pHead,map);
            
        RandomListNode cur = pHead;
        while(cur != null){
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return copyHead;
    }
    
     // 宏观来看: 就是返回拷贝以node为头结点的链表的拷贝 以及next的拷贝
    private RandomListNode process(RandomListNode node, HashMap<RandomListNode,RandomListNode>map){
        if(node == null){
            return null;
        }
        RandomListNode copyNode = new RandomListNode(node.label);
        map.put(node,copyNode);
        copyNode.next = process(node.next,map);
        return copyNode;
    }

O(1)空间的写法:

    public RandomListNode Clone(RandomListNode pHead){
       if(pHead == null)
            return null;
        
        RandomListNode cur = pHead,next = null;
        
        //先拷贝一份原来的链表
        while(cur != null){
            next = cur.next;  //先存着之前的next
            cur.next = new RandomListNode(cur.label);
            cur.next.next = next;
            cur = next;
        }
        
        //复制结点的random指针
        cur = pHead;
        RandomListNode copyCur = null;
        while(cur != null){
            next = cur.next.next; //保存原来链表中的下一个
            copyCur = cur.next; //复制链表的cur
            copyCur.random = cur.random != null ? cur.random.next : null;
            cur = next;
        }
        
        //拆开两个链表
        RandomListNode copyHead = pHead.next;
        cur = pHead;
        while(cur != null){
            next = cur.next.next;
            copyCur = cur.next;
            cur.next = next;
            copyCur.next = next != null ? next.next : null;
            cur = next;
        }
        return copyHead;
    }
    

第二题:二叉搜索树与双向链表

题目链接
题目

在这里插入图片描述
转换示例:
在这里插入图片描述

解析
  • 方法一: 改进中序非递归遍历(注意使用pre保存上一个访问的结点),这个只要会非递归的中序遍历,其实是最简单的方法,每次记录上一个访问的结点,然后每次当前结点为空的时候,就设置pre和cur的关系即可;
  • 方法二(没有使用pre结点): 中序递归的时候,每次递归完之后,返回的是左右孩子都变成了双向链表,并且返回的是变成双向链表的根节点,然后要做的操作就是: a. 找到左边返回的树中的最右边的结点;b.找到右边返回的树中的最左边的结点;c.将这两个结点和当前的根节点的关系连接起来;
    例如:
    在这里插入图片描述
  • 方法三(递归+pre结点): 这个也是使用pre结点,不过是将非递归改成递归, 但是要注意Java中参数传递的是引用类型,所以要使用数组或者全局变量来记录pre;
  • 方法四(递归+连接双向链表): 这个是左神的解法,是递归的改进版本,上面的方法在递归完成之后,需要向左不断的循环,找到最左边的结点,然后返回,其实可以省掉这个,在递归的时候,设置好返回的链表的左右两端的结点,注意分情况判断;

看下书上的解释:
(1)
在这里插入图片描述
(2)
在这里插入图片描述

方法一: 改进的非递归,关于非递归中序,可以看这个博客
    public TreeNode Convert(TreeNode pRootOfTree) {// 返回排序的双向链表的头结点
        if(pRootOfTree == null)
            return null;
        Stack<TreeNode>s = new Stack<>();
        TreeNode pre = null,cur = pRootOfTree,res = null;
        boolean isFirst = true;
        while(!s.isEmpty() || cur != null){
            if(cur != null){
                s.push(cur);
                cur = cur.left;
            }else {
                cur = s.pop();
                
                if(isFirst){
                    isFirst = false;
                    res = cur;
                    pre = cur;
                }else {
                    pre.right = cur;
                    cur.left = pre;
                    pre = cur;
                }
                cur = cur.right; //当前结点向右
            }
        }
        return res;
    }
方法二: 递归的(没有使用pre结点):
    public TreeNode Convert(TreeNode pRootOfTree) {// 返回排序的双向链表的头结点
        if(pRootOfTree == null)
            return null;
        TreeNode last = convert(pRootOfTree);//返回的还是根结点
        while(last != null && last.left != null)
            last = last.left;
        return last;
    }
    private TreeNode convert(TreeNode root){
        if(root == null)
            return null;
        TreeNode L = convert(root.left);
        TreeNode R = convert(root.right);
        
        //将左子树的最后一个结点(最大结点)和根节点链接起来
        if(L != null){
            while(L.right != null){
                L = L.right;
            }
            L.right = root;
            root.left = L;
        }
        //将右子树的最小的结点和根结点链接起来
        if(R != null){
            while(R.left != null){
                R = R.left;
            }
            R.left = root;
            root.right = R;
        }
        return root;//返回的是链接之后的根节点
    }
方法三(递归+pre结点)

注意这里和C++的不同,Java传递的是引用,所以这里使用数组来保存或者使用一个全局变量。

    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null)
            return null;
        TreeNode[] pre = new TreeNode[1];
        convert(pRootOfTree,pre);//返回的pre 是双向链表的最后一个结点
        TreeNode last = pre[0];
        while(last != null && last.left != null)
            last = last.left;
        return last;
    }
    private void convert(TreeNode root,TreeNode[] pre){
        if(root == null)
            return ;
        convert(root.left,pre);
        root.left = pre[0];
        if(pre[0] != null)
            pre[0].right = root;
        pre[0] = root;
        convert(root.right,pre);
    }

使用全局变量:

public class Solution {
    
    private TreeNode pre;
    
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null)
            return null;
        pre = null;
        convert(pRootOfTree);//返回的pre 是双向链表的最后一个结点
        while(pre != null && pre.left != null)
            pre = pre.left;
        return pre;
    }
    private void convert(TreeNode root){
        if(root == null)
            return ;
        convert(root.left);
        root.left = pre;
        if(pre != null)
            pre.right = root;
        pre = root;
        convert(root.right);
    }
}
方法四(递归+连接双向链表)

不需要最后遍历到第一个结点返回,保持连接的始终是一个双向的链表:

    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null)
            return null;
        TreeNode last = convert(pRootOfTree);
        TreeNode res  = last.right;
        last.right = null;
        return res;
    }
    //功能: 将二叉搜索树转换成一个有序的双向链表,并返回连接之后的最后一个结点
    private TreeNode convert(TreeNode root){
        if(root == null)
            return null;
        TreeNode endL = convert(root.left);  //左边的结尾的结点
        TreeNode endR = convert(root.right); //右边的结尾的结点
        
        TreeNode startL = endL != null ? endL.right : null; //左边开始的结点
        TreeNode startR = endR != null ? endR.right : null; //右边开始的结点

        //分情况连接这些结点
        if(endL != null && endR != null){
            //连接根节点的
            root.left = endL;
            endL.right = root;
            root.right = startR;
            startR.left = root;
            //最后一个指回去
            endR.right = startL;
            return endR;
        }else if(endL != null){//右边为空
            root.left = endL;
            endL.right = root;
            root.right = startL;
            return root;
        }else if(endR != null){//左边为空
            root.right = startR;
            startR.left = root;
            endR.right = root;
            return endR;
        }else {  //左右都是空
            root.right = root;
            return root;
        }
    }

第三题: 字符串的排列

题目链接
题目

在这里插入图片描述

解析

四种写法:
这个题目解析可以看下LeetCode46LeetCode47
方法一:

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String>res = new ArrayList<>();
        if(str != null && str.length() != 0){
            process(str.toCharArray(),0,res);
            Collections.sort(res);
        }
        return res;
    }
    
    private void process(char[] str,int cur,ArrayList<String>res){
        if(cur == str.length-1){
            if(!res.contains(String.valueOf(str)))
                res.add(String.valueOf(str));
        }else for(int i = cur; i < str.length; i++){
            swap(str,cur,i);
            process(str,cur+1,res);
            swap(str,cur,i);
        }
    }
    
    private void swap(char[] arr,int i,int j){
        char t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

方法二:
注意这个需要在process之前对数组进行排序,这里虽然在牛客网上不排序也能通过,但是看下面的例子:
输入str : “ddad”
如果不排序: 输出

[ddad, ddda, dadd, ddda, ddad, addd, ddda, ddad, dadd]

上面的有重复,排序之后,输出:

[addd, dadd, ddad, ddda]
import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    
    public ArrayList<String> Permutation(String str) {
        ArrayList<String>res = new ArrayList<>();
        if(str != null && str.length() != 0){
            char[] charArr = str.toCharArray();
            Arrays.sort(charArr);//注意这里需要排序,虽然牛客网的可以通过,但是有问题
            process(charArr,0,res);
        }
        return res;
    }
    
    private void process(char[] str,int cur,ArrayList<String>res){
        char[] newStr = new char[str.length];
        for(int i = 0; i < str.length; i++)
            newStr[i] = str[i];
        if(cur == newStr.length-1){
            res.add(String.valueOf(newStr));
        }else for(int i = cur; i < newStr.length; i++){
            if(cur != i && newStr[cur] == newStr[i])
                continue;
            swap(newStr,cur,i);
            process(newStr,cur+1,res);
            //swap(newStr,cur,i);//这里不能交换,不然得不到字典序
        }
    }

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

方法三:
使用HashSet去重

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String>res = new ArrayList<>();
        if(str != null && str.length() != 0){
            process(str.toCharArray(),0,res);
            Collections.sort(res); //这个也是在之后排序
        }
        return res;
    }
    
    //使用Set去重
    private void process(char[] str,int cur,ArrayList<String>res){
        if(cur == str.length-1){
            res.add(String.valueOf(str));
        }else {
            HashSet<Character>set = new HashSet<>();
            for(int i = cur; i < str.length; i++){
                if(!set.contains(str[i])){
                    set.add(str[i]);
                    swap(str,cur,i);
                    process(str,cur+1,res);
                    swap(str,cur,i);
                }
            }
        }
    }
    
    private void swap(char[] arr,int i,int j){
        char t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

方法四: 深搜

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String>res = new ArrayList<>();
        if(str != null && str.length() != 0){
            char[] charArr = str.toCharArray();
            Arrays.sort(charArr);//保证相邻的元素在一块
            dfs(res,new ArrayList<>(),charArr,new boolean[str.length()]);
        }
        return res;
    }
    
    private void dfs(ArrayList<String>res,ArrayList<Character>tmp,char[] str,boolean[] used){
        if(tmp.size() == str.length){
            String s = "";
            for(Character c : tmp) s += c;
            res.add(s);
        }else for(int i = 0; i < str.length; i++){
            if(used[i] || (i > 0 && !used[i-1] && str[i] == str[i-1]))//去重
               continue;
            used[i] = true;
            tmp.add(str[i]);
            
            dfs(res,tmp,str,used);
            
            used[i] = false;
            tmp.remove(tmp.size()-1);
        }
    }
    
    private void swap(char[] str,int i,int j){
        char t = str[i];
        str[i] = str[j];
        str[j] = t;
    }
}


第四题:数组中出现次数超过一半的数字

题目链接
题目

在这里插入图片描述

解析

这个题目和LeetCode-215. Kth Largest Element in an Array和像,有兴趣的可以看看那篇博客。
三种方法:

  • 方法一: 直接使用额外的空间HashMap保存出现的次数;
  • 方法二: 利用快排的partition过程;
  • 方法三: 巧妙的利用数组的特点的O(n)算法;

代码如下:

方法一: 使用map来保存每个元素出现的次数,只要某个元素次数超过array.length/2就返回,很简单;

import java.util.HashMap;
public class Solution {
    public int MoreThanHalfNum_Solution(int[] array) {
        if(array == null || array.length == 0)
            return 0;
        if(array.length == 1)
            return array[0];
        HashMap<Integer,Integer>map = new HashMap<>();
        for(int i = 0; i < array.length; i++){
            if(map.containsKey(array[i])){
                map.put(array[i],map.get(array[i]) + 1);
                if(map.get(array[i]) > array.length / 2)
                    return array[i];
            }else {
                map.put(array[i],1);
            }
        }
        return 0;
    }
}

方法二:
使用类似快速排序的思想:

  • 对于次数超过一半的数字,则数组中的中位数一定是该数字,(如果数组中真的存在次数超过一半的数字),时间复杂度为O(n);

关于快速排序可以看这篇博客

  • 注意这里不是三路快排(返回的不是等于区域的两个下标),而是<=key的在[L,border]之间,>key的在[border+1,R]之间,而arr[border] = key(划分数),因为我模仿的是快排,最后交换了>区域的最后一个数和arr[more]和划分数arr[R];
public class Solution {
    //对于次数超过一半的数字,则数组中的中位数一定是该数字,(如果数组中真的存在次数超过一半的数字),时间复杂度为O(n)
    public int MoreThanHalfNum_Solution(int[] array) {
        if(array.length == 0 || array == null)
            return 0;
        if(array.length == 1)
            return array[0];
        int L = 0,R = array.length - 1;
        int border = partition(array,L,R);
        int mid = array.length / 2; //中间位置
        while(border != mid){
            if(mid < border){//mid在左边,去左边找
                R = border - 1;//更新R   // array[border]那个一定等于那个划分数 我模仿的是三路快排,最后swap(R,more)
                border = partition(array,L,R);
            }else {
                L = border + 1;
                border = partition(array,L,R);
            }
        }
        int res = array[mid];
        int times = 0;
        for(int i = 0; i < array.length; i++)
            if(res == array[i])
                times++;
        if(times * 2 <= array.length)
            return 0;
        return res;
    }
    
    private int partition(int[] arr,int L,int R){
        int less = L-1;
        int more = R;
        swap(arr, L + (int)(Math.random() * (R-L+1)) ,R);//随机选取一个数 用来和arr[R]划分
        int key = arr[R];//选取arr[R]作为划分数
        int cur = L;
        while( cur < more ){
            if(arr[cur] < key){
                swap(arr,++less,cur++); //把这个比num小的数放到小于区域的下一个,并且把小于区域扩大一个单位
            }else if(arr[cur] > key){
                //把这个比num大的数放到大于去余的下一个,并且把大于区域扩大一个单位
                //同时,因为从大于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur]
                swap(arr,--more,cur);
            }else{//否则的话就直接移动
                cur++;
            }
        }
        swap(arr,more,R); //把最后那个数(arr[R](划分数))放到中间
        return more;//返回的是<=区域的右边界
    }
    private void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

方法三:

  • 如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多;
  • 在遍历数组时保存两个值:一个是数组中每次遍历的候选值cand,另一个当前候选值的次数times;
  • 遍历时,若当前值它与之前保存的候选值cand相同,则次数tims加1,否则次数减1;若次数为0,则保存下一个新的数字cand,并将新的次数times置为1;
  • 遍历结束后,所保存的数字(剩下的)即为所求。当然还需要判断它是否符合条件(因为有可能没有数字次数>N/2);

看下书上的解释:
在这里插入图片描述

public class Solution {
    public int MoreThanHalfNum_Solution(int[] array) {
        if(array.length == 0 || array == null)
            return 0;
        if(array.length == 1)
            return array[0];
        int cand = 0,times = 0;
        for(int i = 0; i < array.length; i++){
            if(times == 0){
                cand = array[i];
                times = 1;
            }else if(array[i] == cand){//又遇到一个同样的,累加
                times++;
            }else {// times != 0 && array[i] != res
                times--;
            }
        }
        // 最后一定要检验,不一定就是res
        times = 0;
        for(int i = 0; i < array.length; i++)
            if(array[i] == cand)
                times++;
        if(times * 2 > array.length)
            return cand;
        return 0;
    }
}

这个题目有两个变形:

  • 求数组中>n/3的次数的数(最多两个);
  • 求数组中>n/k的次数的数;

这类问题统称为摩尔投票问题:
第一题解析
第一题题目来自LeetCode229-Majority Element II,求出数组中>n/3次数的数。

  • 和>n/2次数的数解题方法很相似,>n/2的候选人cand只有一个,统计次数只有一个times;
  • 而>n/3次数的数解题是设置两个候选人cand1和cand2,并且设计两个统计次数count1和count2,按照类似的方法统计;
  • 按照投票的说法,大于n/3次数的解题方法是: 先选出两个候选人cand1,cand2,如果投cand1,则cand1的票数count1++,如果投cand2,则cand2的票数count2++,如果既不投cand1,也不投cand2,那么检查此时是否cand1和cand2候选人的票数是否已经为0,如果为0,则需要更换新的候选人;如果都不为0,则cand1和cand2的票数都要减一;当然最后也需要看看是否两个候选人的票数超过nums.length / 3;

LeetCode229题解:

class Solution {
    public List<Integer> majorityElement(int[] nums) {
        List<Integer>res = new ArrayList<>();
        if(nums == null || nums.length == 0)
            return res;
        if(nums.length == 1){
            res.add(nums[0]);
            return res;
        }
        
        int cand1 = 0,cand2 = 0;
        int count1 = 0,count2 = 0;
        
        for(int i = 0; i < nums.length; i++){
            if(nums[i] == cand1){
                count1++;
            }else if(nums[i] == cand2){
                count2++;
            }else if(count1 == 0){
                cand1 = nums[i];
                count1 = 1;
            }else if(count2 == 0){
                cand2 = nums[i];
                count2 = 1;
            }else { // count1 != 0 && nums[i] != cand1 && count2 != 0 && cand2 != nums[i]
                count1--;
                count2--;
            }
        }
        
        //此时选出了两个候选人,需要检查
        count1 = 0; count2 = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] == cand1){
                count1++;
            }else if(nums[i] == cand2){
                count2++;
            }
        }
        if(count1 > nums.length/3)
            res.add(cand1);
        if(count2 > nums.length/3)
            res.add(cand2);
        return res;
    }
}

第二题解析
上面是超过n/2、n/3次的,如果需要求所有超过n/k次的,则需要时间O(n*k),空间O(k);
在这里插入图片描述

会了>n/2的和>n/3的,则>n/k的也是类推的,使用一个map来保存k个候选人即可:
这里使用LeetCode229来测试我们的求>n/k的程序:

import java.util.Map.Entry;
class Solution {
    public List<Integer> majorityElement(int[] nums) {
        return printKMajority(nums,3);
    }
    //找出数组中出现次数 > N/K的, 创建空间为O(k)的候选人的集合
    public List<Integer> printKMajority(int[] arr,int k){
        ArrayList<Integer>res = new ArrayList<>();
        if(k < 2)
            return res;
        
        HashMap<Integer,Integer>cands = new HashMap<>();
        
        for(int i = 0; i < arr.length; i++){
            if(cands.containsKey(arr[i])){//在候选人的集合中有这个候选人了
                cands.put(arr[i],cands.get(arr[i]) + 1);//给他的票数+1
            }else {                       //与所有的候选人都不同
                //候选人的集合已经满了(当前是第K个),要把所有的候选人的票数减一,如果某些人的票数是1就要移除这个候选人
                if(cands.size() == k-1){
                    ArrayList<Integer>removeList = new ArrayList<>();
                    for(Entry<Integer,Integer>entry : cands.entrySet()){
                        Integer key = entry.getKey();
                        Integer value = entry.getValue();
                        if(value == 1){
                            removeList.add(key);
                        }else {
                            cands.put(key,value-1);
                        }
                    }
                    //删除那些value = 1的候选人
                    for(Integer removeKey : removeList)
                        cands.remove(removeKey);
                }else {     //没有满,把这个加入候选人的集合
                    cands.put(arr[i],1);
                }
            }
        }
        
        //检查候选人是否真的满足条件
        for(Entry<Integer,Integer>entry : cands.entrySet()){
            Integer key = entry.getKey();
            int sum = 0;
            for(int i = 0; i < arr.length; i++){
                if(arr[i] == key)
                    sum++;
            }
            if(sum > arr.length/k)
                res.add(key);
        }
        return res;
    }
    
}

第五题:最小的k个数

题目链接
题目

在这里插入图片描述

解析

大体有两种思路:

  • 使用快速排序类似partition的过程,概率复杂度可以做到O(n)(BFPRT算法可以稳定做到O(N)),和快排不同,这个递归的时候只需要去某一边,但是快排两边都要去;这种方法修改了原数组;
  • 使用堆排序,使用最大堆维护K个数(堆顶最大),一直保持堆中有K个最小的数,时间复杂度N*logK,也可以使用最小堆来做;
思路一: 使用类似快排的partition

先用master公式看看时间复杂度:
在这里插入图片描述
这种思路就是不停的划分,直到我们的border(分界点) = K-1,这时,<=K-1位置的数就是最小的K个数,每次只需要往一边:

  • 如果我们选的划分数很好(在中间): 则T(N) = T(N/2) + O(N) (注意不是2*T(N/2),因为只需要往某一边走),即时间复杂度为: O(N);
  • 如果我们选的划分数很差(极端) : 则T(N) = T(N-1) + O(N) 时间复杂度为: O(N2);
  • 但是概率平均复杂度为O(N);

代码如下:
非递归的 :

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer>res = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length)
            return res;
        int L = 0,R = input.length - 1;
        int border = partition(input,L,R);
        while(border != k-1){//注意第K小的就是划分到k-1(下标)个
            if(k-1 < border){
                R = border - 1;
                border = partition(input,L,R);
            }else {
                L = border + 1;
                border = partition(input,L,R);
            }
        }
        for(int i = 0; i < k; i++)
            res.add(input[i]);
        return res;
    }
    private int partition(int[] arr,int L,int R){
        int less = L-1;
        int more = R;
        swap(arr, L + (int)(Math.random() * (R-L+1)) ,R);//随机选取一个数 用来和arr[R]划分
        int key = arr[R];//选取arr[R]作为划分数
        int cur = L;
        while( cur < more ){
            if(arr[cur] < key){
                swap(arr,++less,cur++); //把这个比num小的数放到小于区域的下一个,并且把小于区域扩大一个单位
            }else if(arr[cur] > key){
                //把这个比num大的数放到大于去余的下一个,并且把大于区域扩大一个单位
                //同时,因为从大于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur]
                swap(arr,--more,cur);
            }else{//否则的话就直接移动
                cur++;
            }
        }
        swap(arr,more,R);//把最后那个数(arr[R](划分数))放到中间
        return more;     //返回的是<=区域的右边界
    }
    private void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

递归的:

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer>res = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length)
            return res;
        process(input,0,input.length-1,k);
        for(int i = 0; i < k; i++)
            res.add(input[i]);
        return res;
    }
    private void process(int[] arr,int L,int R,int k){
        int border = partition(arr,L,R);
        if(k-1 == border)//划分结束 可以返回退出了
            return;
        if(k-1 < border){
            process(arr,L,border-1,k);
        }else {
            process(arr,border+1,R,k);
        }
    }
    private int partition(int[] arr,int L,int R){
        int less = L-1;
        int more = R;
        swap(arr, L + (int)(Math.random() * (R-L+1)) ,R);//随机选取一个数 用来和arr[R]划分
        int key = arr[R];//选取arr[R]作为划分数
        int cur = L;
        while( cur < more ){
            if(arr[cur] < key){
                swap(arr,++less,cur++); //把这个比num小的数放到小于区域的下一个,并且把小于区域扩大一个单位
            }else if(arr[cur] > key){
                //把这个比num大的数放到大于去余的下一个,并且把大于区域扩大一个单位
                //同时,因为从大于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur]
                swap(arr,--more,cur);
            }else{//否则的话就直接移动
                cur++;
            }
        }
        swap(arr,more,R);//把最后那个数(arr[R](划分数))放到中间
        return more;     //返回的是<=区域的右边界
    }
    private void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}
思路二: 使用最大堆

一: 使用PriorityQueue

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
    
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer>res = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length )
            return res;
        PriorityQueue<Integer>maxHeap = new PriorityQueue<>(//维护了一个最大堆(堆顶是最大的)
            new Comparator<Integer>(){
                @Override
                public int compare(Integer o1,Integer o2){
                    return o1 < o2 ? 1 : (o1 == o2 ? 0 : -1);
                    //return -o1.compareTo(o2);
                }
            }
        );
        
        for(int i = 0; i < input.length; i++){
            if(maxHeap.size() < k){//不足k个数,直接加入堆
                maxHeap.add(input[i]);
            }else if(input[i] < maxHeap.peek()){
                maxHeap.poll();
                maxHeap.add(input[i]);
            }
        }
        
        for(Integer item : maxHeap)
            res.add(item);
        return res;
    }
}

二: 手写一个大根堆

import java.util.ArrayList;
public class Solution {
    
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer>res = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length)
            return res;
        int[] kHeap = new int[k];
        for(int i = 0; i < k; i++) // 先用k个数建成一个最大堆
            siftUp(kHeap,input[i],i);
        for(int i = k; i < input.length; i++){
            if(input[i] < kHeap[0]){
                kHeap[0] = input[i];
                siftDown(kHeap,0,k);
            }
        }
        for(int i = 0; i < k; i++)
            res.add(kHeap[i]);
        return res;
    }
     
    //非递归,上浮  //这是最大堆 
    private void siftUp(int[] arr,int num,int i){
        arr[i] = num;
        while(arr[i] > arr[(i-1)/2]){
            swap(arr,i,(i-1)/2);
            i = (i-1)/2;
        }
    }
    
    //非递归调整 下沉 //这是最大堆
    private void siftDown(int[] arr,int i,int heapSize){
        int L = 2 * i + 1;
        while( L < heapSize){
            int maxIdx = L+1 < heapSize && arr[L+1] > arr[L] ? L + 1 : L;//选出左右孩子中最大的
            maxIdx = arr[i] > arr[maxIdx] ? i : maxIdx;
            if(maxIdx == i)
                break;
            swap(arr,maxIdx,i);
            i = maxIdx;
            L = 2*i + 1;
        }
    }
    
    private void swap(int[] arr,int i,int j){ 
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

三: 上面的方法是自己重新建了一个堆(开了O(k)的额外的空间),其实也可以直接在input数组中建堆(修改了原数组),并且建堆的时候,直接从第一个非叶子结点开始建,也就是heapfiy的加速过程,这样就不需要siftUp的过程,一开始就是从第一个非叶子( (k-1)-1) / 2结点直接siftDown: 而且这里我把siftDown写成递归的形式。

import java.util.ArrayList;
public class Solution {
    
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer>res = new ArrayList<>();
        if(input == null || k <= 0 || k > input.length)
            return res;
        for(int i = (k-1-1)/2; i >= 0; i--)//一个k个数的堆,从第一个非叶子结点开始调整 (k-1-1)/2 本来是(k-1)/2,但是下标是k-1 
            siftDown(input,i,k);
        for(int i = k; i < input.length; i++){
            if(input[i] < input[0]){
                swap(input,i,0);
                siftDown(input,0,k);
            }
        }
        for(int i = 0; i < k; i++)
            res.add(input[i]);
        return res;
    }
     
    //递归调整 下沉 //这是最大堆
    private void siftDown(int[] arr,int i,int heapSize){
        int L = 2 * i + 1;
        int R = 2 * i + 2;
        int maxIdx = i;
        if(L < heapSize && arr[L] > arr[maxIdx]) maxIdx = L;
        if(R < heapSize && arr[R] > arr[maxIdx]) maxIdx = R;
        if(maxIdx != i){
            swap(arr,i,maxIdx);
            siftDown(arr,maxIdx,heapSize);//继续调整孩子
        }
    }
    
    private void swap(int[] arr,int i,int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

连续子数组的最大和

题目链接
题目

在这里插入图片描述

解析
这个题目和LeetCode-53. Maximum Subarray一模一样,可以看那篇博客,那篇博客还写了一个求端点的扩展,不重复解释。

递归:

public class Solution {
    private int res;
    
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array == null || array.length == 0)
            return 0;
        res = array[0];
        process(array,array.length-1);
        return res;
    }
    
    private int process(int[] arr,int i){
        if(i == 0)
            return arr[0];
        else {
            int pre = process(arr,i-1); //你先给我求出前面的最大子序和
            int cur = pre > 0 ? pre + arr[i] : arr[i];
            res = Math.max(res,cur);
            return cur;
        }
    }
}

一维dp:

public class Solution {
    //最大连续子序列的和
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array == null || array.length == 0)
            return 0;
        int[] ends = new int[array.length];
        ends[0] = array[0];
        int res = array[0];
        for(int i = 1; i < array.length; i++){
            ends[i] = ends[i-1] > 0 ? ends[i-1] + array[i] : array[i];
            res = Math.max(res,ends[i]);
        }
        return res;
    }
}

滚动优化:

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array == null || array.length == 0)
            return 0;
        int res = array[0];
        int preMax = array[0];
        for(int i = 1; i < array.length; i++){
            preMax = preMax > 0 ? array[i] + preMax : array[i];
            res = Math.max(res,preMax);
        }
        return res;
    }
}

分治,这里要注意边界LMax = process(arr,L,mid);不是mid - 1,因为边界是L == R返回的arr[L]。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array == null || array.length == 0)
            return 0;
        return process(array,0,array.length-1);
    }
    
    //返回这个之间的最大子序和
    private int process(int[] arr,int L,int R){
        if(L == R)
            return arr[L];
        int mid = L + (R - L)/2;
        int LMax = process(arr,L,mid);
        int RMax = process(arr,mid+1,R);
        
        int sum = 0,LSumMax = Integer.MIN_VALUE,RSumMax = Integer.MIN_VALUE;
        
        for(int i = mid; i >= L; i--){
            sum += arr[i];
            if(sum > LSumMax){
                LSumMax = sum;
            }
        }
        sum = 0;
        for(int i = mid + 1; i <= R; i++){
            sum += arr[i];
            if(sum > RSumMax){
                RSumMax = sum;
            }
        }
        int crossMax = LSumMax + RSumMax;
        
        //compare crossMax、LMax,RMax
        if(LMax >= RMax && LMax >= crossMax)
            return LMax;
        if(RMax >= LMax && RMax >= crossMax)
            return RMax;
        return crossMax;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值