题目要求:
给定一个整数数组 nums ,小李想将 nums 切割成若干个非空子数组,使得每个子数组最左边的数和最右边的数的最大公约数大于 1 。为了减少他的工作量,请求出最少可以切成多少个子数组。
示例 1:
输入:nums = [2,3,3,2,3,3]
输出:2
解释:最优切割为 [2,3,3,2] 和 [3,3] 。第一个子数组头尾数字的最大公约数为 2 ,第二个子数组头尾数字的最大公约数为 3 。
示例 2:
输入:nums = [2,3,5,7]
输出:4
解释:只有一种可行的切割:[2], [3], [5], [7]
限制:
1 <= nums.length <= 10^5
2 <= nums[i] <= 10^6
方法一
思路:动态规划,上三角存储。bp[i][j]表示从i到j最小分割次数。
关系式:bp[i][j]=min(bp[i][k]+bp[k][j]) 其中i<=k<=j。
这里给的是一种思路。其实不用二维数组,一维数组即可。
代码如下(超时了。)
public static void main(String[] args) {
// TODO Auto-generated method stub
// int f[]= {2,3,3,2,3,3};
int f[]= {326614,489921};
// int []f= {2,3,3};
System.out.println(splitArray(f));
}
private static List<Integer> list;
private static int [][] bp;
public static int splitArray(int[] nums) {
int i=0,j;
int len=nums.length;
bp=new int[len+1][len+1];
setPrimeNumber();
for(i=1;i<=len;i++)
bp[i][i]=1;
for(i=2;i<=len;i++) {
for(j=i-1;j>0;j--) {
if(check(nums[j-1],nums[i-1])) { //满足要求 最大公约数大于1
bp[j][i]=1;
}
else if(i-j==1) {
bp[j][i]=2;
}
else {
int min=Integer.MAX_VALUE;
for(int k=j+1;k<=i;k++) {
if(min>bp[j][k-1]+bp[k][i]) {
min=bp[j][k-1]+bp[k][i];
}
}
bp[j][i]=min;
}
}
}
return bp[1][len];
}
public static void setPrimeNumber() {
list=new ArrayList<Integer>();
int len=1000050;
boolean[] state=new boolean[len];
Arrays.fill(state, false);
state[2]=true;
for(int i=2;i<len;i++) {
if(state[i])
continue;
list.add(i);
for(int j=i;j<len;j+=i) {
state[j]=true;
}
}
}
public static boolean check(int n,int m){
if(n==m)
return true;
int min=Math.min(n, m);
int data;
for(int i=0;i<list.size();i++) {
data=list.get(i);
if(data>min)
break;
if(n%data==0&&m%data==0) {
return true;
}
}
return false;
}
方法二
思路:
- 首先遍历一般数组,找到最大值。并且在primefactor中对应nums[i]下标创建list。它用来存储可分解的因子。
- 然后通过质数刷,往已经存放的list中放入它的因子。
- 开始动态规划计算。多一个数会有两种情况:一种是新增的数为一个新数组(1+bp[i-1]),还有一种情况便是新增的数于前面的的某个区间组合成新数组(1+bp[k] 0<=k<=i)。
- 将两种情况的最小值写入bp[i]
- 更新哈希表中的质数启示信息。例如数组{2,3,6},6之前哈希表中数据为{2=0,3=1}。处理完6之后哈希表应该是{2=2,3=2}。因为6时的切分数组更短,为1。
代码如下
public static void main(String[] args) {
// TODO Auto-generated method stub
// int f[]= {2,3,5,7};
// int f[]= {3,5,2,3,2};
// int f[]= {2,2};
System.out.println(splitArray(f));
}
private static boolean judge[];
private static Map<Integer, Integer> primeNumberMap;
public static int splitArray(int[] nums) {
primeNumberMap=new HashMap<Integer, Integer>();//存储质数对应的起始下标
List<Integer> [] primefactor=new List[1000010];// 存取每个i时的因子
// 找到nums中最大值
int max=-1;
for(int i=0;i<nums.length;i++) {
if(nums[i]>max)
max=nums[i];
if(primefactor[nums[i]]==null)
primefactor[nums[i]]=new ArrayList<Integer>();
}
judge=new boolean[max+1];//质数中间变量判断 true为质数,false为其他
Arrays.fill(judge, true);
// 存储质数
for(int i=2;i<=max;i++) {
if(!judge[i])
continue;
primeNumberMap.put(i, -1);// 初始化,
if(primefactor[i]!=null)
primefactor[i].add(i);
for(int j=i+i;j<=max;j+=i) {//把质数对应的i排除在外
judge[j]=false;
if(primefactor[j]!=null)
primefactor[j].add(i);
}
}
int bp[]=new int[nums.length+1];//存储中间结果,从0到i需要的最小步长 偏移为1
bp[0]=0;
int onecount,twocount;
for(int i=0;i<nums.length;i++) {
twocount=1+bp[i];
for(int prime:primefactor[nums[i]]) {
int index=primeNumberMap.get(prime);
if(index==-1){//第一次出现
primeNumberMap.put(prime, i);
onecount=bp[i]+1;
}
else
onecount=bp[index]+1;
if(twocount>onecount)
twocount=onecount;
}
bp[i+1]=twocount;
// 更新
for(int prime:primefactor[nums[i]]) {
int index=primeNumberMap.get(prime);
if(twocount<1+bp[index])
primeNumberMap.put(prime, i);
}
}
return bp[nums.length];
}