1.小虎同学打算在华东区域的多个地区开设某虎工场店,他希望以最少的数量覆盖所有目标地区,并降低成本。为了达到这个目标,他将这些地区抽象为平面上的坐标点,并根据一条简单的规则来判断地区间是否能够连接。
规则很简单:如果两个地区的坐标点在同一行或同一列,就认为它们之间存在道路,可以用工场店覆盖。反之,如果地区坐标不在同一行或同一列,就认为它们之间不存在道路,需要额外的工场店才能覆盖。
现在的问题是:对于一组给定的地区,如果我们希望某虎公司的服务可以覆盖到所有目标地区,那么小虎同学最少需要开设几家工场店呢?
通过合理的规划和计算,我们可以得出最优的答案,帮助小虎同学在最少的工场店数量下,实现最全面的服务覆盖。
输入例子1:[[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
输出例子:1
例子说明:任意一个地区开设工场店,都可以满足服务覆盖。所以,最少需要开设1家工场店。
输入例子2:[[0,0],[0,2],[1,1],[2,0],[2,2]]
输出例子:2
例子说明:[1,1]地区需要单独建设工场店;其它地区,可以任意选择一个地区建设工场店即可完成服务覆盖。所以,最少需要开设2家工场店。
///并查集
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 最少需要开设的工场店数量
* @param cities int整型二维数组 城市坐标
* @return int整型
*/
public int minFactoryStores(int[][] cities) {
int[] farther=new int[cities.length];
for (int i = 0; i < cities.length; i++) {
farther[i] = i;
}
// 遍历所有地区对,合并同一行或同一列的地区
for (int i = 0; i < cities.length; i++) {
for (int j = i + 1; j < cities.length; j++) {
if (cities[i][0] == cities[j][0] || cities[i][1] == cities[j][1]) {
union(farther,i, j);
}
}
}
// 统计连通区域数量
int count = 0;
for (int i = 0; i < cities.length; i++) {
if (find(farther,i) == i) {
count++;
}
}
return count;
}
public int find(int[] farther,int x) {
if (farther[x] != x) {
farther[x] = find(farther,farther[x]);
}
return farther[x];
}
public void union(int[] farther,int x, int y) {
int rootX = find(farther,x);
int rootY = find(farther,y);
if (rootX != rootY) {
farther[rootX] = rootY;
}
}
2.二维数组求左上角到最下角的最短路径和/成本。
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 给出一个仓库库位二维分布平面图,计算从仓库的最左上角开始拣货,到最右下角拣货完成的成本最少并返回。
* @param grid int整型二维数组 表示仓库库位二维分布平面图。其中,grid[i][j] 表示在地点 (i, j) 的成本
* @return int整型
*/
public int minPathSum (int[][] grid) {
if(grid.length==1 && grid[0].length==1){
return grid[0][0];
}
// write code here
int[][] dp= new int[grid.length][grid[0].length];
dp[0][0]=grid[0][0];
for(int i=1;i<grid.length;i++){
dp[i][0]=dp[i-1][0]+grid[i][0];
}
for(int j=1;j<grid[0].length;j++){
dp[0][j]=dp[0][j-1]+grid[0][j];
}
for(int i=1;i<grid.length;i++){
for(int j=1;j<grid[0].length;j++){
dp[i][j]=grid[i][j]+Math.min(dp[i-1][j],dp[i][j-1]);
}
}
return dp[grid.length-1][grid[0].length-1];
}
3.途小虎平台提供的以下几个服务及其价格和持续时间(模拟),用户可以根据自己的预算和可用时间选择合适的服务:
服务 | 价格 | 持续时间 |
轮胎安装 | 20 | 1 |
汽车保养 | 50 | 2 |
汽车清洗 | 30 | 1 |
汽车美容 | 60 | 3 |
汽车维修 | 40 | 2 |
现在,给定用户的预算和可用时间,请推荐用户可以选择的所有服务组合,使得总价格不超过预算,总时间不超过可用时间,并且总价格尽可能大。(三维动态规划不会!!!)
/**这个Java代码首先定义了一个Service类来表示服务及其价格和持续时间。findCombinations方法接受服务列表、预算和可用时间,并返回所有不超过预算和时间的服务组合列表。backtrack方法是一个递归方法,用于生成所有可能的服务组合。在main方法中,我们初始化服务列表、用户预算和可用时间,并调用findCombinations方法来找到所有可能的服务组合,并将它们打印出来。
请注意,这个实现没有特别优化以找到总价格最大的组合。它简单地生成了所有可能的组合,并检查它们是否满足预算和时间的限制。在实际应用中,您可能需要添加额外的逻辑来优化搜索过程,以便更高效地找到总价格最大的组合。*/
public class ServiceCombinationFinder {
static class Service {
String name;
int price;
int duration;
Service(String name, int price, int duration) {
this.name = name;
this.price = price;
this.duration = duration;
}
}
public static List<List<Service>> findCombinations(List<Service> services, int budget, int time) {
List<List<Service>> combinations = new ArrayList<>();
backtrack(services, 0, budget, time, new ArrayList<>(), combinations);
return combinations;
}
private static void backtrack(List<Service> services, int start, int remainingBudget, int remainingTime,
List<Service> currentCombination, List<List<Service>> combinations) {
// 如果当前组合的总价格不超过预算且总时间不超过可用时间,则保存这个组合
if (remainingBudget >= 0 && remainingTime >= 0) {
combinations.add(new ArrayList<>(currentCombination));
}
// 尝试添加每个服务到当前组合中
for (int i = start; i < services.size(); i++) {
Service service = services.get(i);
int cost = service.price;
int timeCost = service.duration;
// 如果添加当前服务会导致预算或时间超出限制,则跳过
if (cost > remainingBudget || timeCost > remainingTime) {
continue;
}
// 添加当前服务到组合中
currentCombination.add(service);
// 递归调用,尝试添加下一个服务
backtrack(services, i + 1, remainingBudget - cost, remainingTime - timeCost, currentCombination, combinations);
// 回溯,移除当前服务
currentCombination.remove(currentCombination.size() - 1);
}
}
public static void main(String[] args) {
// 初始化服务列表
List<Service> services = new ArrayList<>();
services.add(new Service("轮胎安装", 20, 1));
services.add(new Service("汽车保养", 50, 2));
services.add(new Service("汽车清洗", 30, 1));
services.add(new Service("汽车美容", 60, 3));
services.add(new Service("汽车维修", 40, 2));
// 用户预算和可用时间
int budget = 100;
int time = 4;
// 查找所有可能的服务组合
List<List<Service>> combinations = findCombinations(services, budget, time);
// 打印所有可能的服务组合
for (List<Service> combination : combinations) {
System.out.println("组合:");
for (Service service : combination) {
System.out.println(" " + service.name + " - 价格: " + service.price + " - 持续时间: " + service.duration);
}
System.out.println();
}
}
}
3.小红拿到了一个正整数,她准备随机选择该数的一个数位,并将这个数位随机改成'1'~'9'中任意一个数字(请注意,修改后可能和原数相同)。小红想知道,她修改一次后,这个数变成素数的概率是多少?
输入描述:
一个正整数 , 1<=n<=10的9次方
注意:整数的最大范围为2147483648=2 * 10的9次方,所以这一题会溢出,但由于质数都是大于0,所以溢出就不用计入总数。
输出描述:
一个浮点数,代表最终得到素数的概率。如果你的答案和标准答案的相对误差不超过10^-5,则认为你的答案正确。
private static int calculateDigit(int n){
int count=0;
while(n!=0){
count++;
n/=10;
}
return count;
}
private static boolean isPrime(int n){
if(n<=1){
return false;
}
for(int i=2;i<n/i;i++){
if(n%i==0){
return false;
}
}
return true;
}
public static void main(String[] args) {
//素数等于质数,都大于1
Scanner in = new Scanner(System.in);
int num = in.nextInt();
int countDigit=calculateDigit(num); //计算位数
int primeCount=0;
for(int i=0;i<countDigit;i++){
int factor=(int)Math.pow(10,countDigit-1-i);
int digit=(num/factor)%10;
for(int j=1;j<=9;j++){
int newNum=num-factor*digit+j*factor;
if(isPrime(newNum)){
primeCount++;
}
}
}
System.out.println(primeCount); //素数个数/总个数,总个数=位数*9
System.out.print((double) primeCount/(countDigit*9));//一定要转double不然会直接成0
}
4.假如有一排房子,共 n
个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3
的正整数矩阵 costs
来表示的。
例如,costs[0][0]
表示第 0 号房子粉刷成红色的成本花费;costs[1][2]
表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。
注意:两个循环的顺序可以互换,来解决问题。
//空间为O(n)
public int minCost(int[][] costs) {
int[][] dp = new int[costs.length][3];
for (int j = 0; j < 3; j++) {
dp[0][j] = costs[0][j];
}
for (int j = 1; j < costs.length; j++) {
for (int i = 0; i < 3; i++) {
dp[j][i] = Math.min(dp[j-1][(i + 1) % 3], dp[j-1][(i + 2) % 3]) + costs[j][i];
}
}
return Math.min(dp[costs.length - 1][0], Math.min(dp[costs.length - 1][1], dp[costs.length - 1][2]));
}
//空间为O(1)
public int minCost(int[][] costs) {
int[][] dp = new int[costs.length][3];
for (int j = 0; j < 3; j++) {
dp[0][j] = costs[0][j];
}
//设定三个状态转移方程,r(i)=min(g(i-1),b(i-1))
//g(i)=min(r(i-1),b(i-1)),b(i)=min(g(i-1),r(i-1))
//画出n*3的表格
for (int j = 1; j < costs.length; j++) {
for (int i = 0; i < 3; i++) {
dp[j][i] = Math.min(dp[j-1][(i + 1) % 3], dp[j-1][(i + 2) % 3]) + costs[j][i];
}
}
return Math.min(dp[costs.length - 1][0], Math.min(dp[costs.length - 1][1], dp[costs.length - 1][2]));
}
5.打家劫舍
一个专业的小偷,计划偷窃一个环形街道上沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组 nums
,请计算 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
Arrays.copyOfRange()函数的的使用,不包括最后一位下标的复制
public int rob(int[] nums) {
//环形房屋注意,第一家偷盗了,最后一家就不能偷盗了
//将问题分解为两个子问题进行动态规划,房屋0到n-1与房屋1到n,求二者的最大值
if(nums.length==0){
return 0;
}
if(nums.length==1){
return nums[0];
}
int[] a1=Arrays.copyOfRange(nums,0,nums.length-1);
int[] a2=Arrays.copyOfRange(nums,1,nums.length);
return Math.max(Rob1(a1),Rob1(a2));
}
private int Rob1(int[] nums){
if(nums.length==1){
return nums[0];
}
int[] dp=new int[2];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<nums.length;i++){
dp[i%2]=Math.max(dp[(i-1)%2],dp[(i-2)%2]+nums[i]);
}
return dp[(nums.length-1)%2];
}
6.给定一个字符串 s
和一个字符串 t
,计算在 s
的子序列中 t
出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE"
是 "ABCDE"
的一个子序列,而 "AEC"
不是)
题目数据保证答案符合 32 位带符号整数范围。
1.解法一
//确认动态转移方程,是双序列问题,一般都是和字符串有关,一般数组长度到len+1
//为什么从-1开始呢,因为从开始状态转移方程不具备普遍性。
//形式为dp[i+1][j+1]=dp[i][j]的样子
/*
当f(i,j)表示字符串s1下标从0到i的字串能否和s2下标从0到j的字串
组成形成s3下标从0到i+j+1的字串.
当下标i不等于下标j,f(i,j)=f(i-1,j-1)
当下标i等于下标j,f(i,j)=f(i-1,j-1)+f(i-1,j)
f(-1,j)=0,f(i,-1)=1,f(-1,-1)=1
*/
int len1=text1.length();
int len2=text2.length();
if(len1<len2){
return 0;
}
int[][] dp=new int[len1+1][len1+1];
for(int i=0;i<len1;i++){
dp[i][0]=1;
}
for(int j=0;j<len2;j++){
dp[0][j]=0;
}
dp[0][0]=1;
for(int i=0;i<len1;i++){
for(int j=0;j<len2 ;j++){
if(text1.charAt(i)==text2.charAt(j)){
dp[i+1][j+1]=dp[i][j]+dp[i][j+1];
}else{
dp[i+1][j+1]=dp[i][j+1];
}
}
}
return dp[len1][len2];
}
2.解法二,一位数组+两个变量保存左上方和正上方值
public int numDistinct(String text1, String text2) {
//在计算第i列时,需要计算左上方和正上方的值,我们选用两个变量来保存
//prev和cur,一定要先保存prev的值再将cur的值赋值给dp[j+1]
int len1=text1.length();
int len2=text2.length();
if(len1<len2){
return 0;
}
int[] dp=new int[len2+1];
dp[0]=1;
for(int i=0;i<len1;i++){
int prev=dp[0];
for(int j=0;j<len2 && i>=j ;j++){
int cur;
if(text1.charAt(i)==text2.charAt(j)){
cur=prev+dp[j+1];
}else{
cur=dp[j+1];
}
prev=dp[j+1];
dp[j+1]=cur;
}
}
return dp[len2];
}
3.解法三,倒叙保存,先更新第最后一行,由于第i列同时存储着第f(i-1,j)和f(i-1,j-1)的值,我们又需要这两个值,所以从右往左不会覆盖f(i-1,j-1)的值
public int numDistinct(String text1, String text2) {
int len1=text1.length();
int len2=text2.length();
if(len1<len2){
return 0;
}
int[] dp=new int[len2+1];
dp[0]=1;
for(int i=0;i<len1;i++){
for(int j=len2-1;j>=0 ;j--){
if(text1.charAt(i)==text2.charAt(j)){
dp[j+1]=dp[j+1]+dp[j];
}
}
}
return dp[len2];
}
7.给定三个字符串 s1
、s2
、s3
,请判断 s3
能不能由 s1
和 s2
交织(交错) 组成。
两个字符串 s
和 t
交织 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
- 交织 是
s1 + t1 + s2 + t2 + s3 + t3 + ...
或者t1 + s1 + t2 + s2 + t3 + s3 + ...
提示:a + b
意味着字符串 a
和 b
连接。
public boolean isInterleave(String s1, String s2, String s3) {
if (s1.length() + s2.length() != s3.length()) {
return false;
}
boolean[][] dp = new boolean[s1.length() + 1][s2.length() + 1];
dp[0][0] = true;
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i) == s3.charAt(i) && dp[i][0])
dp[i + 1][0] = true;
}
for (int j = 0; j < s2.length(); j++) {
if (s2.charAt(j) == s3.charAt(j) && dp[0][j])
dp[0][j + 1] = true;
}
for (int i = 0; i < s1.length(); i++) {
for (int j = 0; j < s2.length(); j++) {
char ch1 = s1.charAt(i);
char ch2 = s2.charAt(j);
char ch3 = s3.charAt(i + j + 1);
if (ch1 == ch3 && dp[i][j + 1]) {
dp[i + 1][j + 1] = true;
} else if (ch2 == ch3 && dp[i + 1][j]) {
dp[i + 1][j + 1] = true;
}
}
}
return dp[s1.length()][s2.length()];
}
8.给定整数 n
,返回 所有小于非负整数 n
的质数的数量 。
//埃式筛选法,遍历2到根号n,将该区间内所有质数2的倍数的数标记为合数,剩余的为质数
public int countPrimes(int n) {
if (n <= 1) {
return 0;
}
int count = 0;
boolean[] nums=new boolean[n+1];
Arrays.fill(nums,true);
for (int i = 2; i <= n/i; i++) {
if (isPrime(i)) {
for(int j=i*i;j<n;j+=i){
nums[j]=false;
}
}
}
for(int i=2;i<n;i++){
if(nums[i]){
count++;
}
}
return count;
}
private boolean isPrime(int n) {
if (n < 2) {
return false;
} for(int i=2;i*i<=n;i++){会溢出
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}