剑指Offer第三十天
分治算法(困难)
题1:打印从1到最大的n位数
输入数字
n
,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
力扣中该题的返回类型是int[],这就将数据的范围固定为32位内了
但是剑指Offer原题中,返回类型为String,主要用于考察大数问题
//leetcode返回int[]解法
class Solution{
public int[] printNumbers(int n){
int end = (int)Math.pow(10,n) - 1;
int[] res = new int[end];
for(int i = 0; i < end; i++){
res[i] = i+1;
}
return res;
}
}
大数问题
当n较大时,会出现int32越界的问题,超过32位范围的数字无法正常存取值
无论short,int,long都有取值范围的限定,所以在大数问题出现时,大数的表示应用串应该为String
String类型的进位效率较低,9999->10000需要进位4次,而其实该题,生成的列表其实是n位0-9的全排序,所以其实可以避开进位操作,进行递归遍历生成String列表
递归,先固定高位,然后往低位固定
//返回String时,大数问题的解法
class Solution{
//定义返回的结果字符串
StringBuilder sb;
//定义全排序的字符数组
char[] loop = {'0','1','2','3','4','5','6','7','8','9'};
char[] num;
public String printNumbers(int n){
num = new char[n];
for(int i = 1; i <= n; i++){
//当i=1,生成长度为1的字符串,索引位置在最左端为0
//..
dfs(0,i);
}
return sb.deleteCharAt(sb.length()-1).toString();
}
//生成长度为n的字符串,当前位置为index
public void dfs(int index, int n){
if(index == n){
sb.append(new String(num) + ",");
return ;
}
int start;
//高位的去零操作,第一位不能为0,不能写成0213,0321这种形式
if(index == 0){
start = 1;
}else{
start = 0;
}
//开始填数字,假设n=2
for(int i = start; i < 10; i++){
//第一次填充num[0]=loop[i],填充十位上的值
//第二次填充num[1]=loop[i],填充个位上的值
num[index] = loop[i];
//递归填充下一位
dfs(index+1, n);
}
}
}
题2:数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
参考题解: https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/jian-zhi-offer-51-shu-zu-zhong-de-ni-xu-pvn2h/
归并排序和逆序列密切相关
基本上一谈到逆序列,就该想到归并排序,归并过程就能对逆序列个数进行统计
详情可参考题解
class Solution {
//要返回的值定义成全局变量,此处是统计逆序列的个数
int count;
public int reversePairs(int[] nums) {
this.count = 0;
merge(nums, 0, nums.length-1);
return count;
}
//套模板
private void merge(int[] nums, int left, int right){
int mid = left + ((right-left)>>1);
if(left < right){
merge(nums, left, mid);
merge(nums, mid+1, right);
mergeSort(nums, left, mid, right);
}
}
private void mergeSort(int[] nums, int left, int mid, int right){
//定义合并的临时数组
int[] temp = new int[right-left+1];
int index = 0;
int l_index = left, r_index = mid+1;
while(l_index <= mid && r_index <= right){
if(nums[l_index] <= nums[r_index]){
//如果不是逆序列,就数组排序
temp[index++] = nums[l_index++];
}else{
//如果存在逆序对,则进行统计并排序
count += (mid - l_index + 1);
temp[index++] = nums[r_index++];
}
}
//如果两边有任意一边到头了,全部装入临时数组中
while(l_index <= mid){
temp[index++] = nums[l_index++];
}
while(r_index <= right){
temp[index++] = nums[r_index++];
}
//用新数组覆盖原数组
for(int i = 0; i < temp.length; i++){
nums[i+left] = temp[i];
}
}
}