剑指offer31-35
31把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面
public class PrintMinNumber_31 {
public static void main(String[] args) {
PrintMinNumber_31 min = new PrintMinNumber_31();
int[] num = {3,32,321};
String res = min.PrintMinNumber(num);
System.out.println(res);
}
public String PrintMinNumber(int[] numbers) {
for(int i = 0; i < numbers.length; i++) {
for(int j = i + 1; j < numbers.length; j++) {
int sum1 = Integer.valueOf(numbers[i]+""+numbers[j]);
int sum2 = Integer.valueOf(numbers[j]+""+numbers[i]);
if(sum1 > sum2) {
int temp = numbers[i];
numbers[i] = numbers[j];
numbers[j] = temp;
}
}
}
String str = "";
for(int i = 0; i < numbers.length; i++) {
str += numbers[i]+"";
}
return str;
}
}
32丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
丑数能够分解成2^x3^y5^z,
所以只需要把得到的丑数不断地乘以2、3、5之后并放入他们应该放置的位置即可,
而此题的难点就在于如何有序的放在合适的位置。
1乘以 (2、3、5)=2、3、5;2乘以(2、3、5)=4、6、10;3乘以(2、3、5)=6,9,15;5乘以(2、3、5)=10、15、25;
从这里我们可以看到如果不加策略地添加丑数是会有重复并且无序,
而在2x,3y,5z中,如果x=y=z那么最小丑数一定是乘以2的,但关键是有可能存在x》y》z的情况,所以我们要维持三个指针来记录当前乘以2、乘以3、乘以5的最小值,然后当其被选为新的最小值后,要把相应的指针+1;因为这个指针会逐渐遍历整个数组,因此最终数组中的每一个值都会被乘以2、乘以3、乘以5,也就是实现了我们最开始的想法,只不过不是同时成乘以2、3、5,而是在需要的时候乘以2、3、5.
public class GetUglyNumber_32 {
public static void main(String[] args) {
GetUglyNumber_32 ugly = new GetUglyNumber_32();
int index = 7;
int res = ugly.GetUglyNumber_Solution(index);
System.out.println(res);
}
public int GetUglyNumber_Solution(int index) {
int[] result = new int[index];
int p2 = 0; int p3 = 0; int p5 = 0;
result[0] = 1;
for(int i = 1; i < index; i++) {
result[i] = Math.min(result[p2]*2,Math.min(result[p3]*3, result[p5]*5));
if(result[i] == result[p2]*2)p2++;
if(result[i] == result[p3]*3)p3++;
if(result[i] == result[p5]*5)p5++;
}
return result[index-1];
}
}
33第一个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
public class FirstNotRepeatingChar_33 {
public static void main(String[] args) {
FirstNotRepeatingChar_33 first = new FirstNotRepeatingChar_33();
String s = "abcbcjAa";
int res = first.FirstNotRepeatingChar(s);
System.out.println(res);
}
public int FirstNotRepeatingChar(String s) {
if (s.length() == 0)
return -1;
int[] count = new int[123];
for(int i = 0; i < s.length(); i++) {
count[s.charAt(i)]++;
}
for(int i = 0; i < s.length(); i++) {
if(count[s.charAt(i)] == 1) {
return i;
}
}
return -1;
}
}
35 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
在归并排序的过程中 后一个数组的数如小于前一个数组的数,则一定能够构成逆序对且逆序对的数目可计算,因为待归并的两个数组提前已经归并排序过,所以不会出现像前面那样少统计或者多统计的情况出现。
思路:[A,B]中的逆序对=[A]的逆序对+[B]中的逆序对+将A,B混排在一起的逆序对
而将A,B混排在一起的逆序对求解看下面:
public class InversePairs_35 { public static void main(String[] args) { int[] array = {1,2,3,4,5,6,7,0}; InversePairs_35 result = new InversePairs_35(); int res = result.InversePairs2(array); System.out.println(res); } //暴力法 时间复杂度:O(N^2) 空间复杂度:O(1) public int InversePairs(int [] array) { int kmod = 1000000007; int ret = 0; int n = array.length; for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { if (array[i] > array[j]) { ret += 1; ret %= kmod; } } } return ret; } //归并排序 时间复杂度:O(NlogN) //空间复杂度:O(N) public int InversePairs2(int [] array) { MergeSort(array, 0, array.length-1); return cnt; } private int cnt; private void MergeSort(int[] array, int start, int end){ if(start>=end)return; int mid = (start+end)/2; MergeSort(array, start, mid); MergeSort(array, mid+1, end); MergeOne(array, start, mid, end); } private void MergeOne(int[] array, int start, int mid, int end){ int[] temp = new int[end-start+1]; int k=0,i=start,j=mid+1; while(i<=mid && j<= end){ //如果前面的元素小于后面的不能构成逆序对 if(array[i] <= array[j]) temp[k++] = array[i++]; else{ //如果前面的元素大于后面的,那么在前面元素之后的元素都能和后面的元素构成逆序对 temp[k++] = array[j++]; cnt = (cnt + (mid-i+1))%1000000007; } } while(i<= mid) temp[k++] = array[i++]; while(j<=end) temp[k++] = array[j++]; for(int l=0; l<k; l++){ array[start+l] = temp[l]; } } }