算法设计可练习题One

1.众数问题(分治法)

(1)法一:分治法(借助快速排序的思想)

思路:
a. 随机选取一个pivoty,然后利用快排思想找到其对应顺序的位置,同时在遍历的过程中计算Pivoty的重数sum。

b. 比较pivoty当前位置左边有多少数,右边有多少数,如果有小于其重数的那边不用考虑了,有大于重数的就转入这一侧继续第一步的步骤。

c. 在每一次计算出来当前的pivoty的重数时,需要比较与当前记录的最大重数大小关系,实时更新最大重数。

public class mode{
    public static int maxMode=0,maxNum=0;
    public static void main(String[] args) {
        int nums[]=new int[20];
        int min=1,max=8,n=20;
        for(int i=0;i<n;i++){
            nums[i]=new Random().nextInt(max-min)+min;
            System.out.print(nums[i]+"  ");
        }
        find_mode(nums,0,nums.length-1);
        System.out.println("\n+众数 is:"+maxNum);
        System.out.println("\n+重数 is:"+maxMode);
    }
    public static void find_mode(int[] nums,int left,int right){
        if(left>=right) return ;
        int count=1;
        int key=nums[left];
        int i = left, j = right;
        while (i < j) {

            // 在右边找到第一个比key小的值
            while (i < j && nums[j] >=key ) {
                j--;
                if(nums[j]==key)count++;
            }
            nums[i] = nums[j];
            // 在左边找到第一个比key大的值
            while (i < j && nums[i] <= key) {
                i++;
                if(nums[i]==key)count++;
            }
            nums[j] = nums[i];
        }
        nums[i]=key;
        if(count>maxMode){
            maxNum=key;
            maxMode=count;
        }
        if(right-i>count){
            find_mode(nums,i+1,right);
        }
        if(i-left>count){
            find_mode(nums,left,i-1);
        }
    }
}

(1)法二:Hash思想(借助HashMap)
public class mode{
    public static void main(String[] args) {
        int nums[]=new int[20];
        int min=1,max=8,n=20;
        for(int i=0;i<n;i++){
            nums[i]=new Random().nextInt(max-min)+min;
        }
        modeExercise(nums);
    }

    public static void modeExercise(int[] nums) {
        Map<Integer, Integer> recode = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            if (recode.containsKey(nums[i])) {
                recode.replace(nums[i], recode.get(nums[i]) + 1);
            } else {
                recode.put(nums[i], 1);
            }
            System.out.print(nums[i]+"  ");
        }
        int maxKey=0, maxValue=0;
        Iterator iter = recode.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry) iter.next();
            int key = (int) entry.getKey();
            int val = (int) entry.getValue();
            if(val>maxValue){
                maxKey=key;
                maxValue=val;
            }
        }
        System.out.println("\n众数是:"+maxKey+"\n重数是:"+maxValue);
    }
}

2. 最近点对问题

题目:对平面上给定的N个点,给出所有点对的最短距离即,输入是平面上的N个点,输出是N点中具有最短距离的两点

(1)要求随机生成N个点的平面坐标,应用穷举法编程计算出所有点对的最短距离(暴力法)
public class mode {
    public static int x[],y[];
    public static void main(String[] args) {
        int N;
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入N:");
        N=sc.nextInt();
        x=new int[N];
        y=new int[N];
        int recordOne=0,recordTwo=0;
        System.out.println("请输入N个点对:");
        for(int i=0;i<N;i++){
            x[i]=sc.nextInt();
            y[i]=sc.nextInt();
        }
        int minDist=Integer.MAX_VALUE;
        for(int i=0;i<N;i++){
            for(int j=i+1;j<N;j++){
                int Dist= (int) (Math.pow(x[i]-x[j],2)+Math.pow(y[i]-y[j],2));
                if(Dist<minDist){
                    recordOne=i;
                    recordTwo=j;
                    minDist=Dist;
                }
            }
        }
        System.out.println("最近的两个点为:");
        System.out.print("("+x[recordOne]+","+y[recordOne]+")     ");
        System.out.print("("+x[recordTwo]+","+y[recordTwo]+")     ");
    }
}
(2)要求随机生成N个点的平面坐标,应用分治法编程计算出所有点对的最短距离

与一维类似,二维最近点问题分治法思想也是先将这些点按照x值从小到大排序。
a.第一步:找到mid=(left+right)/2,接下来最近点要么在(leftmid)中,要么在(mid+1right)中,要么一个在左侧一个在右侧。
b.第二步:难点!!!最近点一个在左侧一个在右侧该怎么找?其实就是跟左侧、右侧最近距离的最小值d比较,找是否存在左侧和右侧距离小于d得值,如果存再必然x1值在[mid-d,mid]中间和x2在[mid,mid+d]中间,两个点的y值只差小于d
c.找到这些点后直接计算距离并更新最短距离以及点即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const double INF = 1e20;
const int N=100;

struct Point {
	double x;
	double y;
}point[N];

int n;
int tmpt[N];

bool cmpxy(const Point& a, const Point& b)
{
    if (a.x != b.x)
        return a.x < b.x;
    return a.y < b.y;
}

bool cmpy(const int& a, const int& b)
{
    return point[a].y < point[b].y;
}

double min(double a, double b)
{
    return a < b ? a : b;
}

double dis(int i, int j)
{
    return sqrt(pow((point[i].x - point[j].x),2)+pow((point[i].y - point[j].y),2));
}

double* Closest_Pair(int left, int right)
{
    double d = INF;
    double* index=new double[3];
    if (left == right) {
        index[0] = -1;
        index[1] = -1;
        index[2] = d;
        return index;
    }
    if (left + 1 == right) {
        index[0] = left;
        index[1] = right;
        index[2] = dis(left, right);
        return index;
    }
    int mid = (left + right) /2;
    double* d1 = Closest_Pair(left, mid);
    double* d2 = Closest_Pair(mid + 1, right);
    if (d1[2] <= d2[2]) {
        index[0] = d1[0];
        index[1] = d1[1];
        index[2] = d1[2];
    }
    else {
        index[0] = d2[0];
        index[1] = d2[1];
        index[2] = d2[2];
    }
    d = index[2];
    int i, j, k = 0;
    //分离出宽度为d的区间
    for (i = left; i <= right; i++)
    {
        if (fabs(point[mid].x - point[i].x) <= d)
            tmpt[k++] = i;
    }
    sort(tmpt, tmpt + k, cmpy);
    //线性扫描
    for (i = 0; i < k; i++)
    {
        for (j = i + 1; j < k && (point[tmpt[j]].y - point[tmpt[i]].y < d); j++)
        {
            double d3 = dis(tmpt[i], tmpt[j]);
            if (d3<d) {
                d = d3;
                index[0]=i;
                index[1]=j;
                index[2]=d3;
            }
        }
    }
    return index;
}


int main()
{
    while (true)
    {
        cout << "请输入n:";
        cin >> n;
        if (n == 0)
            break;
        for (int i = 0; i < n; i++) {
            cin >> point[i].x >> point[i].y;
        }
        sort(point, point + n, cmpxy);
        Point index[2];
        double* result = Closest_Pair(0, n - 1);
        cout<<"\n最近距离为:"<<result[2]<<endl;
        cout << "最近的两个点为:(" << point[(int)result[0]].x << "," << point[(int)result[0]].y << ")";
        cout << "、(" << point[(int)result[1]].x << ", " << point[(int)result[1]].y << ")"<<endl;
    }
    return 0;

}

2. 随机给出一个整数序列,选出其中连续且非空的一段使得这段和最大,要求使用分治法解决

(1)法一:最简单的是使用动态规划法:dp[i]表示以i结尾的子序列的最大和
class Solution {
    public int maxSubArray(int[] nums) {
       //动态规划,dp[i]为以i结尾的子序列的最大和
       int a0;
       a0=nums[0];
       int max=a0;
       for(int i=1;i<nums.length;i++){
           a0=a0>0?(a0+nums[i]):nums[i];
           max=Math.max(max,a0);
       }
       return max;
    }       
}
(2)法二:分治法

这个题目用分治法难点在于每个问题分成两个字问题后剩余的操作,也就是如果最长子序列一部分在左部,一部分在右部,那应该怎么做,此时需要遍历了,一直找,找左侧以mid开始连续最大子序列以及右侧以mid+1开始的连续最大子序列,两个之和就是maxMid,但在这里要注意left right以及right-left1时的情况,如果不简单操作会导致时间超时。

class Solution {
    public int maxSubArray(int[] nums) {
       //分治思想
       return findMaxSubArray(nums,0,nums.length-1);
    }    
    public int findMaxSubArray(int[] nums,int left,int right){
        if(left==right) return nums[left];
        if(right-left==1){
            return Math.max(Math.max(nums[left],nums[right]),nums[left]+nums[right]);
        }
        int mid=(right-left)/2+left;
        int maxLeft=findMaxSubArray(nums,left,mid);
        int maxRight=findMaxSubArray(nums,mid+1,right);
        int maxMid,sum;
        int maxL=nums[mid];
        sum=0;
        for(int i=mid;i>=0;i--){
            sum+=nums[i];
            maxL=Math.max(maxL,sum);
        }
        int maxR=nums[mid+1];
        sum=0;
        for(int i=mid+1;i<=right;i++){
            sum+=nums[i];
            maxR=Math.max(maxR,sum);
        }
        maxMid=maxL+maxR;
        return Math.max(Math.max(maxLeft,maxRight),maxMid);
    }   
}

3.不无聊序列

给定一个序列,若该序列任意的连续子序列中都存在至少一个元素不与其他元素重复,则称该序列为不无聊序列
给定一个长度为N(N<=200000)的数列,问该数列是否为不无聊序列

主要思路:对于当前序列,如果能找到一个在i位置的字符只出现了一次,那么所有包含这个字符的连续子序列都是不无聊序列,接下来就是分治思想,去找序列[left,i-1]和[i+1,right]继续上述操作,但要注意我们需要先初始化每个字符离自己最近的相同字符,左右两边都要找。

public class mode {
    public  static int l[],r[];
    public static void main(String[] args) {
        int n;
        int nums[];
        Scanner sc=new Scanner(System.in);
        System.out.print("请输入n:");
        n=sc.nextInt();
        nums=new int[n];
        System.out.print("\n请输入n个数据:");
        for(int i=0;i<n;i++){
            nums[i]= sc.nextInt();
        }
        l=new int[n];
        r=new int[n];
        init(nums);
        boolean result=solve(0,n-1);
        System.out.println(result);
    }
    public static void init(int[] nums){
        Map<Integer, Integer> map=new HashMap<>();
        map.clear();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(nums[i])){
                l[i]=map.get(nums[i]);
            }else{
                l[i]=-1;
            }
            if(map.containsKey(nums[i])) map.replace(nums[i],i);
            else map.put(nums[i],i);

        }
        map.clear();
        for(int j=nums.length-1;j>=0;j--){
            if(map.containsKey(nums[j])){
                r[j]=map.get(nums[j]);
            }else{
                r[j]=nums.length;
            }
            if(map.containsKey(nums[j])) map.replace(nums[j],j);
            else map.put(nums[j],j);
        }
    }
    public static boolean solve(int left,int right) {
        if (left >= right) return true;
        int i=left,j=right;
        while(i<=j){
            if(l[i] < left && r[i] > right){
                return solve(left, i - 1) && solve(i + 1, right);
            }
            i++;
            if(l[j] < left && r[j] > right){
                return solve(left, j - 1) && solve(j + 1, right);
            }
            j--;
        }
        return false;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值