算法篇——递归与分治例题(众数、逆序对、不无聊序列)

316 阅读3分钟

一.求众数

思路:

  1. 排序(调用系统中的排序算法,效率较高,自己写的话可以用归并)
  2. 如果某个数字为众数,那么排序后他会占主体部分,所以用index定位到中间是又很大概率能找到,假设找打的数为a
  3. 左右扫描,找出该数字的左右边界下边,就是这一堆a左边到哪,右边到哪,并计算该数字的重数,并将它初始化为Mode,以后找到重数更多的遍覆盖掉它
  4. 分治,计算a左走两边数组的长度(不含a),如果长度都比a的重数少就可以确定里面肯定没有众数,如果长度比a大就递归步骤3
import java.util.Arrays;

/**
 * @author SJ
 * @date 2020/10/10
 */
public class FindMode {
    public static void main(String[] args) {
        int[] nums={1,2,2,3,3,4,4,4,4,3,3,3};
        findMode(nums);

    }
    public static void findMode(int[] nums){
        Arrays.sort(nums);
        Mode mode=fun(nums,0,nums.length-1);
        System.out.println("众数是:"+mode.value+"重数是:"+mode.num);


    }
    public static Mode fun(int[] nums,int left,int right){
        int index=(right+left)/2;
        
        int leftbound=findLeftIndex(nums,index,0);
        int rigntbound=findRightIndex(nums,index,right);

        int middleNums=rigntbound-leftbound+1;
        Mode currentMode = new Mode(nums[index], middleNums);
        if ((leftbound-left)>middleNums&&left<leftbound){
            int temp=fun(nums,left,leftbound-1).num;
            if (temp>middleNums){
                currentMode.num=temp;
                currentMode.value=fun(nums,left,leftbound-1).value;
            }

        }
        if ((right-rigntbound)>currentMode.num&&rigntbound<right){
           int temp;
            temp = fun(nums,rigntbound+1,right).num;
            if (temp>middleNums){
            currentMode.num=temp;
            currentMode.value=fun(nums,rigntbound+1,right).value;}

        }
        return currentMode;

    }
    public static int findLeftIndex(int[] nums,int index,int left){
        int leftBound=index;
        for (int i = index-1; i >=left; i--) {
            if(nums[i]==nums[index])
                leftBound=i;
            else
                break;


        }
        return leftBound;

    }
    public static int findRightIndex(int[] nums,int index,int right){
        int rightBound=index;
        for (int i = index+1; i <=right; i++) {
            if(nums[i]==nums[index])
                rightBound=i;
            else
                break;


        }
        return rightBound;

    }
    public static class Mode{
        public int value;
        public  int num=0;

        public Mode(int value, int num) {
            this.value = value;
            this.num = num;
        }
    }


}

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
众数是:3重数是:5

Process finished with exit code 0

二. 逆序对

image-20201011205900036
import java.util.Arrays;

/**
 * @author SJ
 * @date 2020/10/10
 */
public class ReversePairs {
    public static void main(String[] args) {
        int[] nums={7,5,6,4};
        int num=mergeSort(nums,0,nums.length-1);
        //答案是5
        System.out.println(num);
    }
    public  static  Integer[] temp=new Integer[10];

    public static int merge(int[] nums,int left,int right){
        int count=0;
        for (int i = left; i <=right ; i++) {
            temp[i]=nums[i];
        }
        int middle=(left+right)/2;
        int l=left;
        int r=middle+1;

        for (int i = left; i<=right  ; i++) {
            if (l==middle+1&&r<right+1)
                nums[i]=temp[r++];
            else if (r==right+1&&l<middle+1)
                nums[i]=temp[l++];
            else if (temp[l]<=temp[r])
                nums[i]=temp[l++];
            else {
                nums[i]=temp[r++];
                //右边得数组,r指得那个数字 在右边。按理说左边数组所有的数字都比它小,但是只有l前面的数字比它小
                // ,从l到middle(闭区间)所有的数字都比他大。所以从l到middle 与当前r指的数字都构成逆序对
                count+=middle-l+1;
            }

        }
        return count;

    }
    public static int mergeSort(int[] nums,int left,int right){
        int leftCount=0;
        int rightCount=0;
        int aa=0;
        if (left==right)
            return 0;
        else if (left<right){
           leftCount= mergeSort(nums,left,(left+right)/2);
           rightCount=mergeSort(nums,(left+right)/2+1,right);
           aa= merge(nums,left,right);

        }
        return leftCount+rightCount+aa;
    }


}
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" 
5

Process finished with exit code 0

一开始用静态变量count计算的,但是wa了,因为下一组测试用例还是会在上一组的count上累加。改了改之后通过了。

三.不无聊序列

img

首先,如果这个序列(包含n个整数)是不无聊序列list,那么它必然包含一个数 list[k] 在此序列里只出现一次。

接下来我们只要判断list[0]~list[k-1] 到 list[k+1]~list[n-1]是不是不无聊序列就行。

触底->窗口减小到2,且这两个数字不相同。

首先,如何找到这个只出现一次的数呢?暴力寻找试试?

暴力法:用map记录每个数字出现的次数,找出只出现一次的,作为中间值,开始递归重复

import java.util.*;

/**
 * @author SJ
 * @date 2020/10/12
 */
public class SimpleNoBoring {

    public static void main(String[] args) {
        int[] nums = {1, 2, 3,  2, 1,2,2};
        if (isNoBoring(nums, 0, nums.length - 1))
            System.out.println("NoBoring!");
        else
            System.out.println("Boring!");

    }
    static class Num {
        public int nums = 0;//出现频次
        public List<Integer> index;//同一个数据出现的下标列表

        public Num(int nums, int i) {
            this.nums = nums;
            index = new ArrayList<>();
            index.add(i);
        }
    }

    //判断无不无聊
    public static boolean isNoBoring(int[] nums, int left, int right) {
        //划分到只剩一个的情况,不无聊
        if (left==right)
            return true;
        //划分到只剩两个的情况,只要两个数字不相同,就不无聊
        if (right - left == 1 ) {
            if (nums[left]==nums[right])
                return false;
        }
        //划分三个以上的情况
        else {
            int middle = findGreatestIndex(nums,left,right);
            if (middle==-1)//找不到唯一存在的数字
                return false;
            if (middle == left)//唯一存在的数字在最左边
                return isNoBoring(nums, middle + 1, right);
            else if (middle == right)//唯一存在的数字在最右边
                return isNoBoring(nums, left, middle - 1);
            else {//唯一存在的数字在中间,则继续划分
                return isNoBoring(nums, 0, middle - 1) && isNoBoring(nums, middle + 1, right);
            }
        }
        return true;

    }
    //找唯一数据,找到就返回下标,找不到就返回-1
    public static int findGreatestIndex(int[] nums,int left,int right) {
        //key是数字的值
        Map<Integer, Num> map = new HashMap<>();
        for (int i = left; i <= right; i++) {
            if (map.containsKey(nums[i])) {
                Num num = map.get(nums[i]);
                num.nums++;
                num.index.add(i);
            } else
                map.put(nums[i], new Num(1, i));
        }
        Set<Integer> set = map.keySet();
        for (Integer integer : set) {
            if (map.get(integer).nums == 1) {
                return map.get(integer).index.get(0);
            }
        }
          return -1;

    }

}

其实想了想之后,上面的程序第一次扫描就已经记录了该数组的所有信息(包括数字,该数字出现的次数,值为该数字的所有下标),根本不需要每次递归都new一个map来记录。我们每次递归只要从第一次new的map里找信息就可以了。

尝试一下。

。。。。

尝试失败了。

//list里保存了所有单独的数字。
//判断list里有没有某个数x,使得left<=x<=right,如果有,证明该范围内有唯一数字,继续拆分

递归拆分之后只要是left~right范围内唯一就好了,不一定是全局范围内唯一,而list记录的是全局唯一的数字的下标,所有我这个想法是错的。