组成目标货币的最少张数

news2025/1/16 2:01:48

1、题目

arr 是货币数组,其中的值都是正数。再给定一个正数 aim

每个值都认为是一张货币,返回组成 aim 的最少张数。

注意:因为是求张数,所以每张货币认为是相同或不同就不重要了。

2、思路

假设 arr = [3,1,3,5,1,1,1,5,3,2]

则去重后的货币数组[3,1,5,2]

每种货币的张数数组[3,4,2,1]

尝试方式:讨论每个位置的货币用 i 张的情况,比如 0 位置的货币(3元)分别用 0 张、1 张、2 张和 3 张的情况。

  • 经典方法:货币不去重,讨论每张货币用和不用的情况
//经典方法:货币不去重,讨论每张货币用和不用的情况
int minCoins(vector<int> &arr, int aim) {
    return process(arr, 0, aim);
}

//暴力递归
//index位置开始往后货币随意选择,组成rest的最少张数
int process(vector<int> &arr, int index, int rest) {
    if (rest < 0) {
        return INT_MAX;
    }

    if (index == arr.size()) {
        return rest == 0 ? 0 : INT_MAX;
    } else {
        //不使用index位置的货币
        int p1 = process(arr, index + 1, rest);
        //使用index位置的货币
        int p2 = process(arr, index + 1, rest - arr[index]);

        if (p2 != INT_MAX) {
            p2++; //加上index位置这张货币
        }
        return min(p1, p2);
    }
}
  • 暴力递归改成动态规划
//暴力递归修改成动态规划
//O(arr长度 * aim)
int dp1(vector<int> &arr, int aim) {
    if (aim == 0) return 0;

    int n = arr.size();
    int dp[n + 1][aim + 1];
    memset(dp, 0, sizeof(dp));

    dp[n][0] = 0;

    for (int j = 1; j <= aim; j++) {
        dp[n][j] = INT_MAX;
    }

    for (int index = n - 1; index >= 0; index--) {
        for (int rest = 0; rest <= aim; rest++) {
            int p1 = dp[index + 1][rest];
            int p2 = rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : INT_MAX;
            if (p2 != INT_MAX) {
                p2++;
            }
            dp[index][rest] = min(p1, p2);
        }
    }

    return dp[0][aim];
}

假设当前来到了 i i i 位置,而该面值是3元,一共2张, i i i 及其往后的货币自由选择,如果要组成目标货币27元,求最小张数。

分析该位置的求解需要依赖的值:

  1. 如果使用 0 张 i i i 位置的货币,则 i + 1 i + 1 i+1 及其后面的货币自由选择组成目标货币27元,如果需要 a a a 张;

  2. 而如果使用 1 张 i i i 位置的货币(3元), i + 1 i+1 i+1 及其后面的货币自由选择组成27 - 3 = 24元,假设需要 b b b 张,再加上这一张3元,总共就是 b + 1 b + 1 b+1 张;

  3. 如果使用 2 张 i i i 位置的货币(3元), i + 1 i + 1 i+1 及其后面的货币自由选择组成目标24 - 3 = 21元,假设需要 c 张,再加上这两张3元,总共就是 c + 2 c + 2 c+2 张;

待求的位置的结果是 min{a + 0, b + 1, c + 2},此过程涉及到了一个枚举行为。
请添加图片描述
代码实现如下:

  • 货币数组去重
class Info {
public:
    vector<int> coins;
    vector<int> zhangs;

    Info(vector<int> &c, vector<int> &z) {
        coins = c;
        zhangs = z;
    }
};

Info *getInfo(vector<int> &arr) { //得到去重货币数组以及货币张数
    unordered_map<int, int> counts;
    for (int value : arr) {
        if (!counts.count(value)) {
            counts[value] = 1;
        } else {
            counts[value] += 1;
        }
    }

    int n = counts.size();
    vector<int> coins(n);
    vector<int> zhangs(n);

    int index = 0;
    for (auto entry : counts) {
        coins[index] = entry.first;
        zhangs[index++] = entry.second;
    }

    return new Info(coins, zhangs);
}
  • 货币数组去重后的动态规划
//优化:去重版动态规划
//时间复杂度:O(arr长度) + O(货币种数 * aim * 每种货币的平均张数)
int dp2(vector<int> &arr, int aim) {
    if (aim == 0) return 0;

    Info *info = getInfo(arr); //得到info的时间复杂度O(arr长度)
    vector<int> coins = info->coins;
    vector<int> zhangs= info->zhangs;

    int n = coins.size();

    int dp[n + 1][aim + 1];

    memset(dp, 0, sizeof(dp));

    dp[n][0] = 0;

    for (int j = 1; j <= aim; j++) {
        dp[n][j] = INT_MAX;
    }
    //时间复杂度: O(货币种数 * aim * 每种货币的平均张数)
    for (int index = n - 1; index >= 0; index--) {
        for (int rest = 0; rest <= aim; rest++) {
            dp[index][rest] = dp[index + 1][rest];
            for (int zhang = 1; zhang * coins[index] <= aim && zhang <= zhangs[index]; zhang++) { //枚举
                if ((rest - zhang * coins[index] >= 0) && (dp[index + 1][rest - zhang * coins[index]] != INT_MAX)) {
                    dp[index][rest] = min(dp[index][rest], zhang + dp[index + 1][rest - zhang * coins[index]]);
                }
            }
        }
    }

    return dp[0][aim];
}

int compensate(int pre, int cur, int coin) {
    return (cur - pre) / coin;
}

能否省掉这个枚举行为?

看待求位置的前一个位置:同理上面的分析,X位置的结果是 min{b + 0, c + 1, d + 2}

请添加图片描述

根据斜率优化,想省掉这个枚举行为,但是麻烦了。因为待求位置是 min{a + 0, b + 1, c + 2},而x位置是 min{b + 0, c + 1, d + 2},也就是说 x 位置依赖的窗口是 [d, c, b],而待求位置依赖的窗口是 [c, b, a],x并不知道d 滑出窗口后,最小值如何变化。因为 x 并不知道 d + 2, b + 1, a + 0 三个值谁胜出了,x 只记录一个最小值,所以x不知道在d滑出窗口后,最小值如何变化。这就比如果求的依赖的累加和麻烦。

那么到底该怎么变呢?使用窗口内最大值和最小值的更新结构

抽象化:准备一个单调双端队列,存放最小值,即从队首到队尾,值从小到大。如果当前窗口内放着 x 元 a 张(队列内放的张数),而目前 y 元 b 张要入队,那么应该谁和谁比较呢?【a + (y - x) / 面值】 和 【b】 进行比较,就是说在比较的时候,必须是基于钱数相等的情况下,面值小的货币张数就要先补齐到货币值等于钱数。

请添加图片描述
优化代码如下:

  • 优化版本:用到窗口内最小值的更新结构
//时间复杂度: O(arr长度) + O(货币种数 * aim)
int dp3(vector<int> &arr, int aim) {
    if (aim == 0) return 0;

    Info *info = getInfo(arr);
    vector<int> c = info->coins;
    vector<int> z = info->zhangs;

    int n = c.size();

    //静态数组的局限性:占用的栈空间过大,导致无法申请声明数组
    vector<vector<int> > dp(n + 1, vector<int>(aim + 1, 0));

    dp[n][0] = 0;

    for (int j = 1; j <= aim; j++) {
        dp[n][j] = INT_MAX;
    }

    // 虽然是嵌套了很多循环,但是时间复杂度为O(货币种数 * aim)
	// 因为用了窗口内最小值的更新结构
    for (int i = n - 1; i >= 0; i--) {
        for (int mod = 0; mod < min(aim + 1, c[i]); mod++) {
            // 当前面值 X
			// mod  mod + x   mod + 2*x   mod + 3 * x
            deque<int> w;
            w.push_back(mod);
            dp[i][mod] = dp[i + 1][mod];

            for (int r = mod + c[i]; r <= aim; r += c[i]) {
                while (!w.empty() && (dp[i + 1][w.back()] == INT_MAX || dp[i + 1][w.back()] + compensate(w.back(), r, c[i]) >= dp[i + 1][r])) {
                    w.pop_back();
                }
                w.push_back(r);
                int overdue = r - c[i] * (z[i] + 1);
                if (w.front() == overdue) {
                    w.pop_front();
                }

                dp[i][r] = dp[i + 1][w.front()] + compensate(w.front(), r, c[i]);
            }
        }
    }

    return dp[0][aim];
}

优化的好处:如果不压缩数据(去重得到面值数组),那么填表的时间复杂度是 O ( n ∗ a i m ) O(n * aim) O(naim);但是压缩后,假设原数组长度为 n n n,去重得到的面值数组长度为 m m m,那么只需要一个 m ∗ a i m m * aim maim 的表即可(时间复杂度 O ( m ∗ a i m ) O(m * aim) O(maim),且每个格子都是由窗口结构更新而来,时间复杂度为 O ( 1 ) O(1) O(1),当货币出现大量重复的时候,就非常省时间。

本题优化点:张数压缩;用窗口更新结构省掉枚举行为。

3、完整代码

C++ 版

/*************************************************************************
	> File Name: 004.组成目标货币的最少张数.cpp
	> Author: Maureen 
	> Created Time: 三 11/16 15:12:39 2022
 ************************************************************************/

#include <iostream>
#include <vector>
#include <deque>
#include <unordered_map>
#include <ctime>
using namespace std;

//暴力递归
//index位置开始往后货币随意选择,组成rest的最少张数
int process(vector<int> &arr, int index, int rest) {
    if (rest < 0) {
        return INT_MAX;
    }

    if (index == arr.size()) {
        return rest == 0 ? 0 : INT_MAX;
    } else {
        //不使用index位置的货币
        int p1 = process(arr, index + 1, rest);
        //使用index位置的货币
        int p2 = process(arr, index + 1, rest - arr[index]);

        if (p2 != INT_MAX) {
            p2++; //加上index位置这张货币
        }
        return min(p1, p2);
    }
}

//方法1
//经典方法:货币不去重,讨论每张货币用和不用的情况
int minCoins(vector<int> &arr, int aim) {
    return process(arr, 0, aim);
}

//方法2:
//暴力递归修改成动态规划
//O(arr长度 * aim)
int dp1(vector<int> &arr, int aim) {
    if (aim == 0) return 0;

    int n = arr.size();
    int dp[n + 1][aim + 1];
    memset(dp, 0, sizeof(dp));

    dp[n][0] = 0;

    for (int j = 1; j <= aim; j++) {
        dp[n][j] = INT_MAX;
    }

    for (int index = n - 1; index >= 0; index--) {
        for (int rest = 0; rest <= aim; rest++) {
            int p1 = dp[index + 1][rest];
            int p2 = rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : INT_MAX;
            if (p2 != INT_MAX) {
                p2++;
            }
            dp[index][rest] = min(p1, p2);
        }
    }

    return dp[0][aim];
}


class Info {
public:
    vector<int> coins;
    vector<int> zhangs;

    Info(vector<int> &c, vector<int> &z) {
        coins = c;
        zhangs = z;
    }
};

Info *getInfo(vector<int> &arr) { //得到去重货币数组以及货币张数
    unordered_map<int, int> counts;
    for (int value : arr) {
        if (!counts.count(value)) {
            counts[value] = 1;
        } else {
            counts[value] += 1;
        }
    }

    int n = counts.size();
    vector<int> coins(n);
    vector<int> zhangs(n);

    int index = 0;
    for (auto entry : counts) {
        coins[index] = entry.first;
        zhangs[index++] = entry.second;
    }

    return new Info(coins, zhangs);
}

//方法3
//优化:去重版动态规划
//时间复杂度:O(arr长度) + O(货币种数 * aim * 每种货币的平均张数)
int dp2(vector<int> &arr, int aim) {
    if (aim == 0) return 0;

    Info *info = getInfo(arr); //得到info的时间复杂度O(arr长度)
    vector<int> coins = info->coins;
    vector<int> zhangs= info->zhangs;

    int n = coins.size();

    int dp[n + 1][aim + 1];

    memset(dp, 0, sizeof(dp));

    dp[n][0] = 0;

    for (int j = 1; j <= aim; j++) {
        dp[n][j] = INT_MAX;
    }
    //时间复杂度: O(货币种数 * aim * 每种货币的平均张数)
    for (int index = n - 1; index >= 0; index--) {
        for (int rest = 0; rest <= aim; rest++) {
            dp[index][rest] = dp[index + 1][rest];
            for (int zhang = 1; zhang * coins[index] <= aim && zhang <= zhangs[index]; zhang++) { //枚举
                if ((rest - zhang * coins[index] >= 0) && (dp[index + 1][rest - zhang * coins[index]] != INT_MAX)) {
                    dp[index][rest] = min(dp[index][rest], zhang + dp[index + 1][rest - zhang * coins[index]]);
                }
            }
        }
    }

    return dp[0][aim];
}

int compensate(int pre, int cur, int coin) {
    return (cur - pre) / coin;
}
//方法4
//优化需要用到窗口内最小值的更新结构
//时间复杂度: O(arr长度) + O(货币种数 * aim)
int dp3(vector<int> &arr, int aim) {
    if (aim == 0) return 0;

    Info *info = getInfo(arr);
    vector<int> c = info->coins;
    vector<int> z = info->zhangs;

    int n = c.size();

    //静态数组的局限性:占用的栈空间过大,导致无法申请声明数组
    vector<vector<int> > dp(n + 1, vector<int>(aim + 1, 0));

    dp[n][0] = 0;

    for (int j = 1; j <= aim; j++) {
        dp[n][j] = INT_MAX;
    }

    // 虽然是嵌套了很多循环,但是时间复杂度为O(货币种数 * aim)
	// 因为用了窗口内最小值的更新结构
    for (int i = n - 1; i >= 0; i--) {
        for (int mod = 0; mod < min(aim + 1, c[i]); mod++) {
            // 当前面值 X
			// mod  mod + x   mod + 2*x   mod + 3 * x
            deque<int> w;
            w.push_back(mod);
            dp[i][mod] = dp[i + 1][mod];

            for (int r = mod + c[i]; r <= aim; r += c[i]) {
                while (!w.empty() && (dp[i + 1][w.back()] == INT_MAX || dp[i + 1][w.back()] + compensate(w.back(), r, c[i]) >= dp[i + 1][r])) {
                    w.pop_back();
                }
                w.push_back(r);
                int overdue = r - c[i] * (z[i] + 1);
                if (w.front() == overdue) {
                    w.pop_front();
                }

                dp[i][r] = dp[i + 1][w.front()] + compensate(w.front(), r, c[i]);
            }
        }
    }

    return dp[0][aim];
}



//测试代码
vector<int> randomArray(int n, int maxValue) {
    vector<int> arr(n);
    for (int i = 0; i < n; i++) {
        arr[i] = (rand() % maxValue) + 1;
    }

    return arr;
}

void printArray(vector<int> &arr) {
    cout << "arr : ";
    for (int i = 0; i < arr.size(); i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int maxLen = 20;
    int maxValue = 30;
    int testTime = 300000 + 1;

    cout << "Function Test begin:" << endl;
    for (int i = 0; i < testTime; i++) {
        int n = rand() % maxLen;
        vector<int> arr = randomArray(n, maxValue);
        int aim = rand() % maxValue;

        int ans = minCoins(arr, aim);
        int ans1 = dp1(arr, aim);
        int ans2 = dp2(arr, aim);
        int ans3 = dp3(arr, aim);
        
        if (ans != ans1 || ans2 != ans3 || ans != ans2) {
            cout << "Oops!" << endl;
            printArray(arr);
            cout << "aim = " << aim << endl;
            cout << "ans = " << ans << endl;
            cout << "ans1 = " << ans1 << endl;
            cout << "ans2 = " << ans2 << endl;
            cout << "ans3 = " << ans3 << endl;
            break;
        }

        if (i % 10000 == 0) cout << i << " cases passed!" << endl;
    }
    cout << "Function Test ends!" << endl;
    cout << "===============" << endl;



    int aim = 0;
    vector<int> arr;
    long long start;
    long long end;
    int ans2;
    int ans3;
    cout << "Performance Test begin:" << endl;
    maxLen = 30000;
    maxValue = 20;
    aim = 60000;
    arr = randomArray(maxLen, maxValue);

    start = clock();
    ans2 = dp2(arr, aim);
    end = clock();
    cout << "ans2 = " << ans2 << ", dp2 cost " << (end - start) * 1000 / CLOCKS_PER_SEC << " ms" << endl;


    start = clock();
    ans3 = dp3(arr, aim);
    end = clock();
    cout << "ans3 = " << ans3 << ", dp3 cost " << (end - start) * 1000 / CLOCKS_PER_SEC << " ms" << endl;

    cout << "Performance Test ends!" << endl;
    cout << "===============" << endl;


    //以下测试的数据量只有dp3能通过,其它方法不能通过
    cout << "货币大量重复出现的情况下" << endl;
    cout << "大数据量测试滑动窗口方案开始:"<< endl;
    maxLen = 20000000;
    aim = 10000;
    maxValue = 10000;
    arr = randomArray(maxLen, maxValue);

    start = clock();
    ans3 = dp3(arr, aim);
    end = clock();
    cout << "dp3滑动窗口方案运行时间:" << (end - start) * 1000 / CLOCKS_PER_SEC  << " ms" << endl;
    cout << "大数据量测试滑动窗口方案结束" << endl;

    cout << "===============" << endl;

    cout << "当货币很少出现重复,dp2比dp3有常数时间优势" << endl;
    cout << "当货币大量出现重复,dp3时间复杂度明显优于dp2" << endl;
    cout << "dp3的优化用到了窗口最小值的更新结构" << endl;

    return 0;
}

Java 版

public class Code04_MinCoinsOnePaper {

	public static int minCoins(int[] arr, int aim) {
		return process(arr, 0, aim);
	}
	
    //经典方法:货币不去重,讨论每张货币用和不用的情况
    //暴力递归
	public static int process(int[] arr, int index, int rest) {
		if (rest < 0) {
			return Integer.MAX_VALUE;
		}
		if (index == arr.length) {
			return rest == 0 ? 0 : Integer.MAX_VALUE;
		} else {
			int p1 = process(arr, index + 1, rest);
			int p2 = process(arr, index + 1, rest - arr[index]);
			if (p2 != Integer.MAX_VALUE) {
				p2++;
			}
			return Math.min(p1, p2);
		}
	}

    //暴力递归改成的动态规划
	// dp1时间复杂度为:O(arr长度 * aim)
	public static int dp1(int[] arr, int aim) {
		if (aim == 0) {
			return 0;
		}
		int N = arr.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 0;
		for (int j = 1; j <= aim; j++) {
			dp[N][j] = Integer.MAX_VALUE;
		}
		for (int index = N - 1; index >= 0; index--) {
			for (int rest = 0; rest <= aim; rest++) {
				int p1 = dp[index + 1][rest];
				int p2 = rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : Integer.MAX_VALUE;
				if (p2 != Integer.MAX_VALUE) {
					p2++;
				}
				dp[index][rest] = Math.min(p1, p2);
			}
		}
		return dp[0][aim];
	}

	public static class Info {
		public int[] coins; //所有货币去重后的数组
		public int[] zhangs; //每种货币的张数

		public Info(int[] c, int[] z) {
			coins = c;
			zhangs = z;
		}
	}

	public static Info getInfo(int[] arr) {
		HashMap<Integer, Integer> counts = new HashMap<>();
		for (int value : arr) {
			if (!counts.containsKey(value)) {
				counts.put(value, 1);
			} else {
				counts.put(value, counts.get(value) + 1);
			}
		}
		int N = counts.size();
		int[] coins = new int[N];
		int[] zhangs = new int[N];
		int index = 0;
		for (Entry<Integer, Integer> entry : counts.entrySet()) {
			coins[index] = entry.getKey();
			zhangs[index++] = entry.getValue();
		}
		return new Info(coins, zhangs);
	}

	// dp2时间复杂度为:O(arr长度) + O(货币种数 * aim * 每种货币的平均张数)
	public static int dp2(int[] arr, int aim) {
		if (aim == 0) {
			return 0;
		}
		// 得到info时间复杂度O(arr长度)
		Info info = getInfo(arr);
		int[] coins = info.coins;
		int[] zhangs = info.zhangs;
		int N = coins.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 0;
		for (int j = 1; j <= aim; j++) {
			dp[N][j] = Integer.MAX_VALUE;
		}
		// 这三层for循环,时间复杂度为O(货币种数 * aim * 每种货币的平均张数)
		for (int index = N - 1; index >= 0; index--) {
			for (int rest = 0; rest <= aim; rest++) {
				dp[index][rest] = dp[index + 1][rest];
				for (int zhang = 1; zhang * coins[index] <= aim && zhang <= zhangs[index]; zhang++) { //枚举行为
					if (rest - zhang * coins[index] >= 0
							&& dp[index + 1][rest - zhang * coins[index]] != Integer.MAX_VALUE) {
						dp[index][rest] = Math.min(dp[index][rest], zhang + dp[index + 1][rest - zhang * coins[index]]);
					}
				}
			}
		}
		return dp[0][aim];
	}

	// dp3时间复杂度为:O(arr长度) + O(货币种数 * aim)
	// 优化需要用到窗口内最小值的更新结构
	public static int dp3(int[] arr, int aim) {
		if (aim == 0) {
			return 0;
		}
		// 得到info时间复杂度O(arr长度)
		Info info = getInfo(arr);
		int[] c = info.coins;
		int[] z = info.zhangs;
		int N = c.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 0;
		for (int j = 1; j <= aim; j++) {
			dp[N][j] = Integer.MAX_VALUE;
		}
		// 虽然是嵌套了很多循环,但是时间复杂度为O(货币种数 * aim)
		// 因为用了窗口内最小值的更新结构
		for (int i = N - 1; i >= 0; i--) {
            //例如目标货币是30元,而当前货币面值是7元,则先填0,7, 14, 21, 28;然后填1, 8, 15, 22, 29... 分组填
			for (int mod = 0; mod < Math.min(aim + 1, c[i]); mod++) { 
				// 当前面值 X
				// mod  mod + x   mod + 2*x   mod + 3 * x
				LinkedList<Integer> w = new LinkedList<>();
				w.add(mod);
				dp[i][mod] = dp[i + 1][mod];
				for (int r = mod + c[i]; r <= aim; r += c[i]) {
					while (!w.isEmpty() && (dp[i + 1][w.peekLast()] == Integer.MAX_VALUE
							|| dp[i + 1][w.peekLast()] + compensate(w.peekLast(), r, c[i]) >= dp[i + 1][r])) { //入队列的时候要跟补偿后的张数进行比较
						w.pollLast();
					}
					w.addLast(r);
					int overdue = r - c[i] * (z[i] + 1);
					if (w.peekFirst() == overdue) {
						w.pollFirst();
					}
					dp[i][r] = dp[i + 1][w.peekFirst()] + compensate(w.peekFirst(), r, c[i]); //得到最后结果的时候也要进行补偿
				}
			}
		}
		return dp[0][aim];
	}

	public static int compensate(int pre, int cur, int coin) {
		return (cur - pre) / coin;
	}

	// 为了测试
	public static int[] randomArray(int N, int maxValue) {
		int[] arr = new int[N];
		for (int i = 0; i < N; i++) {
			arr[i] = (int) (Math.random() * maxValue) + 1;
		}
		return arr;
	}

	// 为了测试
	public static void printArray(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	// 为了测试
	public static void main(String[] args) {
		int maxLen = 20;
		int maxValue = 30;
		int testTime = 300000;
		System.out.println("功能测试开始");
		for (int i = 0; i < testTime; i++) {
			int N = (int) (Math.random() * maxLen);
			int[] arr = randomArray(N, maxValue);
			int aim = (int) (Math.random() * maxValue);
			int ans1 = minCoins(arr, aim);
			int ans2 = dp1(arr, aim);
			int ans3 = dp2(arr, aim);
			int ans4 = dp3(arr, aim);
			if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) {
				System.out.println("Oops!");
				printArray(arr);
				System.out.println(aim);
				System.out.println(ans1);
				System.out.println(ans2);
				System.out.println(ans3);
				System.out.println(ans4);
				break;
			}
		}
		System.out.println("功能测试结束");

		System.out.println("==========");

		int aim = 0;
		int[] arr = null;
		long start;
		long end;
		int ans2;
		int ans3;

		System.out.println("性能测试开始");
		maxLen = 30000;
		maxValue = 20;
		aim = 60000;
		arr = randomArray(maxLen, maxValue);

		start = System.currentTimeMillis();
		ans2 = dp2(arr, aim);
		end = System.currentTimeMillis();
		System.out.println("dp2答案 : " + ans2 + ", dp2运行时间 : " + (end - start) + " ms");

		start = System.currentTimeMillis();
		ans3 = dp3(arr, aim);
		end = System.currentTimeMillis();
		System.out.println("dp3答案 : " + ans3 + ", dp3运行时间 : " + (end - start) + " ms");
		System.out.println("性能测试结束");

		System.out.println("===========");

		System.out.println("货币大量重复出现情况下,");
		System.out.println("大数据量测试dp3开始");
		maxLen = 20000000;
		aim = 10000;
		maxValue = 10000;
		arr = randomArray(maxLen, maxValue);
		start = System.currentTimeMillis();
		ans3 = dp3(arr, aim);
		end = System.currentTimeMillis();
		System.out.println("dp3运行时间 : " + (end - start) + " ms");
		System.out.println("大数据量测试dp3结束");

		System.out.println("===========");

		System.out.println("当货币很少出现重复,dp2比dp3有常数时间优势");
		System.out.println("当货币大量出现重复,dp3时间复杂度明显优于dp2");
		System.out.println("dp3的优化用到了窗口内最小值的更新结构");
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/10420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GD32F450的时钟笔记

GD32F450 标称 200MHz&#xff0c;但是在手册中又说 它是 240MHz。本文以 手册中的 240MHz 进行举例&#xff0c;我保险起见&#xff0c;产品中使用还是在 200MHz 下使用。 时钟树 手册上的时钟树图如下 GD32F450的 外部时钟源 有2个 LXTAL 外部低速时钟源 32.768 kHzHXTAL …

微信小程序登录获取不到头像和昵称解决办法!

微信小程序登录获取不到头像和昵称主要原因是&#xff1a;小程序wx.getUserProfile接口被收回&#xff01; 大家可以按照文档操作↓ PS&#xff1a; 针对小程序wx.getUserProfile接口将被收回后做出的授权调整 小程序文档中提出的调整说明 对于此次变化&#xff0c;现将小…

怎么使用股票委托下单接口?

都知道&#xff0c;在进行量化交易的时候&#xff0c;交易接口可以100%严格按照定制的计划执行交易&#xff0c;避免了出现下单犹豫和过量交易等问题&#xff0c;100%体现交易策略的收益性&#xff1b;今天主要来聊聊&#xff0c;关于怎么使用股票委托下单接口的问题&#xff1…

使用c#将aj-report桌面化1

说到底,aj-report是个工具,我想大多数人还是想快速使用它来创建一个可以展示的工具。通过之前的章节,你应该可以制作自己的报表页面了,下面我们来看看怎么把aj-report包装成一个桌面能够运行的软件。 当然作为扩展开发,受开源协议限制,我们不能大规模修改aj-report的源代…

[附源码]java毕业设计基于新高考模式下的排课系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

程序员“中年危机”?不,也可能是“人生转机”

程序员行业新技术发展迅猛&#xff0c;可以说是日新月异。也正是这个原因&#xff0c;中年危机成为我们必须面对和攻克的问题。 思考一个问题&#xff1a;那些能工作到45、50、甚至60的程序员们&#xff0c;究竟具备了哪些过人的能力&#xff1f; 就我过去的经历和观察来说&a…

【蓝桥杯真题练习】STEMA科技素养练习题库 答案版015 持续更新中~

1、牛顿运动定律描述了物体运动符合的规律,被誉为经典力学的基石。以下关于牛顿定律的描述中,不正确的选项是( )。 A、牛顿运动定律一共有三条 B根据牛顿定律,没有受到外力作用的物体运动速度会保持不变 C根据牛顿运动定律,两个物体作用于对方的力大小一定相等 D牛…

项目运营后阶段发力

前言 我记得之前有人跑过来问我&#xff1a;你知道为啥字节面试总会考算法吗&#xff1f;我没有回答&#xff0c;一方面是算法是大厂必考的东西&#xff0c;既然是必须的东西究竟个为什么有点白费心机&#xff0c;第二方面我没有进去过字节&#xff0c;所以我也拿捏不准为什么。…

业务数据分析-Excel的基础操作(一)

Excel的基础操作1、工作簿2、工作表3、数据基本操作4、数据类型5、常用快捷键1、工作簿 即Excel文档&#xff0c;用来储存和处理数据的文件 常用扩展名&#xff1a; xls 97-03版xlsx 07版以后xlsm 带宏的文件 一个工作簿包含多张工作表 2、工作表 默认名称&#xff1a;shee…

【小程序】微信小程序自定义组件Component详细总结

1- 前言 在本文中你将收获 小程序如何使用自定义组件自定义组件之间的传值自定义组件中插槽的使用 2- 组件文件新建 2.1 定义组件 在根目录新建components文件夹&#xff0c;建立cell 文件夹&#xff0c;右击创建cell的Component组件 cell.jscell.wxmlcell.jsoncell.wxss …

mmlab花朵分类结果展示(2)

花朵分类结果展示Grad-Cam可视化方法模型分析折线图和平均耗时展示计算量展示这一节我们继续上一节讲解训练结果测试与验证。Grad-Cam可视化方法 上一节我们讲述了数据增强的可视化方法&#xff0c;因为数据增强是训练模型前的步骤&#xff0c;所以即使我们没有训练结果也可以…

水稻插秧机分叉机构壳体零件数控加工工艺工装设计

目录 &#xff08;一&#xff09;产品介绍-------------------------------------------------------------------1 &#xff08;二&#xff09;计算生产纲领-------------------------------------------------------------2 1&#xff0c;计算生产纲领决定生产类型 2&#x…

【k哥爬虫普法】非法入侵计算机信息系统,获取1500万余条个人信息!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZDkxIyG-1668593948055)(https://s2.loli.net/2022/11/07/Lx2ZiWGpanJbOvc.png)] 我国目前并未出台专门针对网络爬虫技术的法律规范&#xff0c;但在司法实践中&#xff0c;相关判决已屡见不鲜&#x…

Mybatis---从入门到深化

一、什么是框架&#xff1f; 框架即一个半成品软件。开发者从头开发一个软件需要花费大量精力&#xff0c;于是有一些项目组开发出半成品软件&#xff0c;开发者在这些软件的基础上进行开发&#xff0c;这样的软件就称之为框架。 如果将开发完成的软件比作是一套已经装修完毕的…

超级实用的电脑小技巧大全

技巧一&#xff1a; 一些用户正在经历缓慢和间歇性的下载速度。 解决方案&#xff1a; 比如小编在下载界面做了温馨提示&#xff0c;要求用户在使用本软件前&#xff0c;退出电脑上安装的任何杀毒软件&#xff08;360安全卫士、QQ管家、金山卫士、百度卫士等&#xff09;&am…

C语言 - 通讯录详解

通讯录 文章目录1. 基本思路2.代码实现2.1 定义各种**宏**和**结构体**。2.2 创建结构体并进行初始化2.3 打印菜单&#xff0c;模拟用户的选择2.4 增加联系人2.5 删除联系人2.6 查找联系人2.7 修改联系人2.8 对通讯录进行升序排序2.9 打印通讯录2.10 结束程序并销毁通讯录3. 文…

再度入榜 | 中睿天下入选《中国网络安全企业100强》

近日&#xff0c;国内权威网络安全媒体、第三方安全咨询机构安全牛发布第十版《中国网络安全企业100强》&#xff08;以下简称“百强”&#xff09;&#xff0c;中睿天下以强大的综合发展实力和强劲的发展势头再次荣登百强榜单。 安全牛作为国内网络安全行业权威媒体&#xff0…

能解决你80%关于存储的疑惑

概述 在计算机系统中&#xff0c;常用的存储介质包括寄存器、内存、SSD、磁盘等&#xff0c;寄存器的速写速度与CPU相同&#xff0c;一个时钟周期是0.3纳秒&#xff0c;而内存访问需要120纳秒&#xff0c;寄存器的读写速度比内存要快几百倍&#xff0c;固态硬盘访问需要50&…

Vue3基础(26)___defineProps、defineEmits、defineExpose组件通信

defineProps、defineEmits、defineExpose组件通信 在使用这个之前&#xff0c;我们需要知道setup的语法糖写法&#xff0c;因为上面的三个api需要在这个语法糖中才能使用&#xff1a; <script setup>console.log(LiuQing) </script>里面的代码会被编译成组件 set…

m分别通过GA遗传优化算法对企业不同产品订单生产进行时间优化

目录 1.算法概述 2.仿真效果预览 3.核心MATLAB代码预览 4.完整MATLAB程序 1.算法概述 首先介绍MATLAB部分的遗传算法的优化算法介绍&#xff1a; 遗传算法的原理 遗传算法GA把问题的解表示成“染色体”&#xff0c;在算法中也即是以二进制编码的串。并且&#xff0c;在执行…