剑指Offer系列之题31~题35
31.连续子数组的最大和
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
根据数组特点,依次向后加,当和小于0时,说明再向后加也会小于下一位,所以直接从下一位开始计算。当和大于0但是小于之前的和时,将之前的和保存,在后面进行对比,若被超过就替换。
1.根据数组:
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
//连续最大和
if(array.length<=0)
return 0;
//数组的特点
int max=array[0];
int temp=array[0];//暂存大于0的和
for(int i=1;i<array.length;++i){
if(max+array[i]<=0){//若当前和小于0,则从下一位重新开始计数
if(max+array[i]>temp)//小于0也需判断是否大于最大值
temp=max+array[i];
max=0;
}else{//若当前和大于0
if(max+array[i]<max){//判断该和是否小于之前的max
if(max+array[i]>temp)
temp=max;//若小于则保存之前的max;
}else if(max+array[i]>temp){
temp=max+array[i];
}
max=max+array[i];
}
}
return temp;
}
}
2.动态规划:
F(i):以array[i]为末尾的子数组的最大值;res:当前所有子数组的最大值
F(i)=Math.max(F(i-1)+array[i],array[i]);以array[i]为末尾的子数组的最大值要么是前面序列+当前数字,要么是当前数字。
res=Math.max(F(i),res);找到所有子数组中的最大值。
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int fi=array[0];//以array[i]为末尾的子数组的最大值
int res=array[0];//当前所有子数组的最大值
for(int i=1;i<array.length;++i){
fi=Math.max(fi+array[i],array[i]);
res=Math.max(fi,res);
}
return res;
}
}
32.1到n的整数中1出现的次数 🔺
求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
暴力解:求/10的余数,然后/10,依次判断每一位是否为1。找规律:
1.暴力解:
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
//1到n中1出现的次数
if(n<=0)
return 0;
int count=0;
for(int i=1;i<=n;++i){
int com=i;
if(com%10==1)//余数为1则个位是1
count++;
while(com/10!=0){
com=com/10;
if(com%10==1){
count++;
}
}
}
return count;
}
}
2.归纳,找规律:
从个位开始分析:0-9;10-19;20-29……每十个数构成一个阶梯,每个阶梯个位上出现1次1。当不构成完整阶梯时,看/10的余数是否大于1,大于则出现一次1,否则不出现。个位1出现的次数可以总结为:
n/10 + (n%10==0? 0:1)
十位:0-99;100-199;200-299……每一百个数构成一个阶梯,每个阶梯十位上出现10次1。当不构成完整阶梯时,看/100的余数是否大于19,大于则出现10次1,小于10则不出现,处于10-19间则出现余数-10+1
次1,十位上1出现的次数可以总结为:(n/100)*10+if(n%100 > 19) 10 else if(n%100 < 10) 0 else (n%100 -10+1)
百位:同上。百位上1出现的次数可以总结为:(n/1000)*100 +if(n%1000 > 199) 100 else if(n%1000 < 100) 0 else (n%1000 -100+1)
ps:若输入Integer.MAX_VALUE会返回一个负值。
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
if(n <= 0)
return 0;
int count = 0;
for(long i = 1; i <= n; i *= 10){
long diviver = i * 10;
count += (n / diviver) * i + Math.min(Math.max(n % diviver - i + 1, 0), i);
}
return count;
}
}
3.剑指offer:
设N = abcde ,其中abcde分别为十进制中各位上的数字。如果要计算百位上1出现的次数,它要受到3方面的影响:百位上的数字,百位以下(低位)的数字,百位以上(高位)的数字。
① 如果百位上数字为0,百位上可能出现1的次数由更高位决定。比如:12013,则可以知道百位出现1的情况可能是:100199,11001199,21002199,,...,1110011199,一共1200个。可以看出是由更高位数字(12)决定,并且等于更高位数字(12)乘以 当前位数(100)。
② 如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:12113,则可以知道百位受高位影响出现的情况是:100199,11001199,21002199,,....,1110011199,一共1200个。和上面情况一样,并且等于更高位数字(12)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:12100~12113,一共114个,等于低位数字(113)+1。
③ 如果百位上数字大于1(29),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100199,11001199,21002199,...,1110011199,1210012199,一共有1300个,并且等于更高位数字+1(12+1)乘以当前位数(100)。
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;//1的个数
int i = 1;//当前位
int current = 0,after = 0,before = 0;
while((n/i)!= 0){
current = (n/i)%10; //高位数字
before = n/(i*10); //当前位数字
after = n-(n/i)*i; //低位数字
//如果为0,出现1的次数由高位决定,等于高位数字 * 当前位数
if (current == 0)
count += before*i;
//如果为1,出现1的次数由高位和低位决定,高位*当前位+低位+1
else if(current == 1)
count += before * i + after + 1;
//如果大于1,出现1的次数由高位决定,//(高位数字+1)* 当前位数
else{
count += (before + 1) * i;
}
//前移一位
i = i*10;
}
return count;
}
}
33.把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
快排,然后从前到后拼接。快排中比较的规则是:将两个数字拼接,哪个拼接后数字小,哪个数字小。
快排(更改比较规则):
import java.util.ArrayList;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if(numbers.length<=0)
return "";
//最小数字,最高位尽可能小,如果第一位相等,则选第二位尽可能小,依次类推
String result="";
//遍历,每一轮找出一个最小值,此处的最小值指排序后的数小,其就是小值
ArrayList<Integer> temp=new ArrayList<>();
//根据比较规则进行排序,然后从头到尾进行拼接
quickSort(numbers,0,numbers.length-1);
for(int i=0;i<numbers.length;++i){
result+=numbers[i];
}
return result;
}
public boolean newCompare(int a,int b){//判断a是否小于b
String left="";
left+=a;
left+=b;
String right="";
right+=b;
right+=a;
if(left.compareTo(right)<0)//可能存在超出int范围的情况,所以利用String的比较
return true;
return false;
}
public void quickSort(int a[],int low,int high){
if(low<high){
int index=partition(a,low,high);
quickSort(a,low,index-1);
quickSort(a,index+1,high);
}
}
public int partition(int a[],int low,int high){
int mid=a[low];
while(low<high){
while(low<high && !newCompare(a[high],a[low]))
high--;
a[low]=a[high];
while(low<high && newCompare(a[low],mid))
low++;
a[high]=a[low];
}
a[low]=mid;
return low;
}
}
34.丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
暴力解:对每个数判断是否满足(可能超时)。
空间换时间。
1.暴力解:
public class Solution {
public int GetUglyNumber_Solution(int index) {
//只包含质因子2,3,5的数叫做丑数
if(index<=0)
return 0;
if(index==1)
return 1;
int n=1;
int p=1;
while(n<index){
p++;
if(isUgly(p))
n++;
}
return p;
}
public boolean isUgly(int p){
while(p%2==0)
p=p/2;
while(p%3==0)
p=p/3;
while(p%5==0)
p=p/5;
if(p==1)
return true;
return false;
}
}
2.空间换时间:
第一位是1,然后第二位是1*2、1*3 、1*5中的最小值2,2进入数组。然后下一位是1*3、 1*5、 2*2 、2*3 、2*5中的最小值。
可以看出:2*3 、2*5是不必要计算的,因为2比1大,前面有1*3、 1*5未进入数组,2*3 、2*5必定比这两个大,所以只需计算2*2。
即此时第一位只用来计算*3,*5,第二位用来*2。当3入组后,第一位只用来*5,第二位用来*2 *3,依此类推。
综上,对*2 、*3 、*5的值分别维护一个指针。当一个值的乘运算是最小值,入数组之后,将其指针后移一位。
public class Solution {
public int GetUglyNumber_Solution(int index) {
//只包含质因子2,3,5的数叫做丑数
if(index<=0)
return 0;
if(index<7)
return index;
int result[]=new int[index];
result[0]=1;
int p2=0;//分别对应*2 3 5的数所在位置
int p3=0;
int p5=0;
for(int i=1;i<index;++i){
//当前位的丑数一定是之前丑数*2,*3,*5里的最小值
result[i]=Math.min(Math.min(result[p2]*2,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];
}
}
35.第一个只出现一次的字符
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
暴力解:利用有序Map存储字符和出现次数,然后遍历找到第一个次数为1的,输出其在字符串中的位置。
利用Ascii码。
1.暴力解:
import java.util.LinkedHashMap;
import java.util.Map;
public class Solution {
public int FirstNotRepeatingChar(String str) {
//有部分只出现一次的字符,找到其中第一个
if(str==null)
return -1;
int len=str.length();
if(len==1)
return 0;
Map<Character,Integer> charMap=new LinkedHashMap<>();//有序map,保证字符加入的顺序
for(int i=0;i<len;++i){//遍历
//判断是否出现过
if(!charMap.containsKey(str.charAt(i))){//没有则以1次入map
charMap.put(str.charAt(i),1);
}else{
int count=charMap.get(str.charAt(i));//获取该字符的出现次数
charMap.put(str.charAt(i),++count);//+1
}
}
for(int i=0;i<len;++i){
if(charMap.get(str.charAt(i)) == 1)
return i;
}
return -1;
}
}
2.Ascii码:
利用每个字母的ASCII码作hash来作为数组的index。
首先用一个长度为58的数组来存储每个字母出现的次数,为什么是58呢,主要是由于A-Z
对应的ASCII码为65-90
,a-z
对应的ASCII码值为97-122
。每个字母的index=int(word)-65
,比如g=103-65=38
。为了大小写统一地计算减65,所以将ASCII码值为91-96
的这六位也计算进去,所以长度为58
而数组中具体记录的内容是该字母出现的次数,最终遍历一遍字符串,找出第一个数组内容为1的字母就可以了,时间复杂度为O(n)。
public class Solution {
public int FirstNotRepeatingChar(String str) {
//有部分只出现一次的字符,找到其中第一个
//根据字符的ascii码
int[] words = new int[58];
for(int i = 0;i<str.length();i++){
words[((int)str.charAt(i))-65] += 1;
}
for(int i=0;i<str.length();i++){
if(words[((int)str.charAt(i))-65]==1)
return i;
}
return -1;
}
}
如有错误,欢迎指正