劍指offer-每日6題之第六天(java版)
原題連結:
第一題:整數中1出現的次數(從1到n整數中1出現的次數)
第二題:把陣列排成最小的數
第三題:醜數
第四題:第一個只出現一次的字元
第五題:陣列中的逆序對
第六題:兩個連結串列的第一個公共結點
第一題:整數中1出現的次數(從1到n整數中1出現的次數)
題目描述
求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?為此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。 |
---|
解析
把1到n分成三部分:如a=12345和b=22345 第一部分和第二部分求23456-12345和23456-22345中的1的個數 第一部分(數的第一位所能包含的1的個數): 因為a的第一位是1,所以,在以1開頭的存在的1的個數有2345+1個,+1是因為有10000 對於b,因為第一位是2,所以,在以1開頭的存在的1的個數有10^4個(從10000到19999) 第二部分(除開第一位,剩下的位數所能包含的1的個數): 對於a除開第一位的剩餘位的數字2345,如果固定一位取1,其他三位可以取0-9中的任意的數,所以,有1*4*10^3 對於b除開第一位的剩餘位的數字2345,如果固定一位取1,其他三位可以取0-9中的任意的數,但是第一位是2,在第一 位為1的時候有1*4*10^3個,在第一位為2的時候也有1*4*10^3個,所以,共有2*4*10^3 第三部分(遍歷1-2345):同樣採用剛才的演算法處理子問題,即用遞迴求解
|
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
if(n<0)return 0;
int one=0,two=0,three=0;
String str=String.valueOf(n);
char []chs=str.toCharArray();
int len=str.length();
if(len==1 && chs[0]=='0')return 0;
else if(len==1 && chs[0]>='1')return 1;
else if(chs[0]>'1') one=(int)Math.pow(10,len-1);
else if(chs[0]=='1')one=Integer.valueOf(str.substring(1))+1;
two=(int)((chs[0]-'0')*(len-1)*Math.pow(10,len-2));
three=NumberOf1Between1AndN_Solution(Integer.valueOf(str.substring(1)));
return one+two+three;
}
}
第二題:把陣列排成最小的數
題目描述
輸入一個正整數陣列,把數組裡所有數字拼接起來排成一個數,列印能拼接出的所有數字中最小的一個。例如輸入陣列{3,32,321},則打印出這三個數字能排成的最小數字為321323。 |
---|
解析
利用集合的排序,排序把兩個元素拼接起來比較大小,按順序排,比如321 32,比較32132和32321,顯然32132比較小,所以321排序後再32的前面,同理可以得到其他的 最後把得到的集合連線成字串 |
import java.util.*;
public class Solution {
public String PrintMinNumber(int [] numbers) {
String res="";
List<Integer>list=new ArrayList<Integer>();
for(int i=0;i<numbers.length;i++){
list.add(numbers[i]);
}
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return (o1+""+o2).compareTo(o2+""+o1);
}
});
for(int i=0;i<numbers.length;i++){
res+=list.get(i);
}
return res;
}
}
第三題:醜數
題目描述
把只包含質因子2、3和5的數稱作醜數(Ugly Number)。例如6、8都是醜數,但14不是,因為它包含質因子7。 習慣上我們把1當做是第一個醜數。求按從小到大的順序的第N個醜數。 |
---|
解析
迴圈判斷從1到n的數是否為醜數 判斷是否為醜數:%2,%3,%5,不為0,n/=2,4,5 (時間複雜度太高,不能通過)
|
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index<=0)return 0;
int number=0;
int uglyFound=0;
while(uglyFound<index){
++number;
if(isUgly(number)){
++uglyFound;
}
}
return number;
}
public boolean isUgly(int num){
while(num%2==0) num/=2;
while(num%5==0) num/=5;
while(num%5==0) num/=5;
return (num==1)?true:false;
}
}
解析
定義一個數組存已經得到的醜數,那麼第一個已經存在的醜數就是1.定義t2,t3,t5;t2的意義為:已有的醜數中最大的數<t2*2 即t2本次醜數陣列中的某個數,t2*2是下一次結果中更新的有關質因子2的最小的醜數,同樣,我們就可以定義出t3,t5,最後比較更新後 t2*2,t3*3,t5*5的最小值就為下一次的最小的醜數
所以,問題是如何找到t2,t3,t5? 在已經得到的醜數中,我們是按順序排列的,所以,要找t2的位置,我們可以知道t2*2得到的數>已有的醜數中最大的數,那麼t2之前的數*2<已有的最大的醜數,我們只需把陣列的的醜數*2得到的結果 和當前的最大的醜數對比,就能定位到t2的位置。 |
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index<=0)return 0;
int[]ugly=new int[index];
ugly[0]=1;
int nextIndex=1;
int t2=0,t3=0,t5=0;
while(nextIndex<index){
int min= min(ugly[t2]*2,ugly[t3]*3,ugly[t5]*5);
ugly[nextIndex]=min;
while(ugly[t2]*2<=ugly[nextIndex])t2++;
while(ugly[t3]*3<=ugly[nextIndex])t3++;
while(ugly[t5]*5<=ugly[nextIndex])t5++;
++nextIndex;
}
return ugly[nextIndex-1];
}
public int min(int a,int b,int c){
int min=a<b?a:b;
return min<c?min:c;
}
}
第四題:第一個只出現一次的字元
題目描述
在一個字串(0<=字串長度<=10000,全部由字母組成)中找到第一個只出現一次的字元,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫) |
---|
解析
定義一個HashMap,把字串新增到map中,value為字串出現的次數 在遍歷一次字串,找到第一個map中值為1的 |
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
Map<Character,Integer>map=new HashMap<Character,Integer>();
int i=0;
for(;i<str.length();i++){
char ch=str.charAt(i);
if(map.containsKey(ch)){
int count=map.get(ch);
map.put(ch,++count);
}else{
map.put(ch,1);
}
}
int index=-1;
for(i=0;i<str.length();i++){
if(map.get(str.charAt(i))==1) {
index = i;
break;
}
}
return index;
}
}
第五題:陣列中的逆序對
題目描述
在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個陣列中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007 |
---|
解析
歸併排序擴充套件: 1.歸併排序: 對於一個無序陣列如:2 3 1 0 4 2 5 6 9 7 平均的分為左部分(2 3 1 0 4)和右部分(2 5 6 9 7) 對左部分和右部分進行排序,排序結果為(0 1 2 3 4)和(2 5 6 9 7) 左部分和右部分排序完成後,呼叫merge函式把兩部分合成一個有序的陣列(用兩個指標指向兩部分開頭,往後遍歷,並用輔助陣列help儲存得到的有序陣列) 左部分和右部分的排序同樣呼叫歸併排序過程,一直劃分為兩個子樹組,當左部分和右部分為一個數不能劃分時(下標相等時),返回
2.如果使用歸併排序解答這道題呢? 對左部分和右部分求逆序對數,最後總的逆序對數=左部分的逆序對數+右部分的逆序對數+merge過程的逆序對數 求解merge的逆序對數: 如左部分 4 5 6 右部分3 4 5 當指標都指向 兩部分開始位置時,4>3,存在逆序對,且4的 右邊的都是3的逆序對,所以對於3,逆序對的個數為mid-0+1=2-0+1;然後右部分指標往後移一個,指向4位置,4=4,不產生逆序對,count+=0;一直這樣遍歷到結束 注意:count在三個部分計算的時候都要P%1000000007,最後一次在三部分相加後也要P%1000000007,防止三個部分相加的值超過P%1000000007
|
public class Solution {
public int InversePairs(int [] array) {
if(array==null||array.length<2)return 0;
int count=sortPress(0,array.length-1,array)%1000000007;
return count;
}
public int merge(int l,int mid,int r,int[] arr){
int []help=new int [r-l+1];
int p1=l;
int p2=mid+1;
int i=0;
int count=0;
while(p1<=mid&&p2<=r){
count+=arr[p1]<=arr[p2]?0:mid-p1+1;
count%=1000000007;
help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=mid)help[i++]=arr[p1++];
while(p2<=r)help[i++]=arr[p2++];
for(int j=0;j<help.length;j++){
arr[j+l]= help[j];
}
return count;
}
public int sortPress(int l,int r,int []arr){
if(l==r)return 0;
int mid=l+((r-l)>>1);
return sortPress(l,mid,arr)%1000000007+sortPress(mid+1,r,arr)%1000000007+merge(l,mid,r,arr);
}
}
六題:兩個連結串列的第一個公共結點
題目描述
輸入兩個連結串列,找出它們的第一個公共結點。 |
---|
解析
方法一: 分別求出兩個連結串列的長度,讓較長的連結串列先走abs(len1-len2)步,然後兩個連結串列一起走,當遇到值相等時,找到了公共節點 |
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
import java.util.*;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null||pHead2==null)return null;
int len1=getLsitLength( pHead1);
int len2=getLsitLength( pHead2);
boolean flag=len1>len2?true:false;
ListNode lon=pHead1;
ListNode sho=pHead2;
if(!flag){
lon=pHead2;
sho=pHead1;
}
int i=0;
while(i<Math.abs(len1-len2)){
lon=lon.next;
i++;
}
while(lon!=null && sho!=null && lon.val!=sho.val){
lon=lon.next;
sho=sho.next;
}
return lon;
}
public int getLsitLength(ListNode pHead){
int len=0;
while(pHead!=null){
len++;
pHead=pHead.next;
}
return len;
}
}
解析
方法二: 定義兩個棧分別儲存兩個連結串列的內容,然後從棧頂開始遍歷,當棧頂的值步相等時,返回上次相等的節點,否則,兩個棧頂同時出棧,直到找到不相等的節點 |
import java.util.*;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null||pHead2==null)return null;
Stack <ListNode>stack1=new Stack();
Stack <ListNode>stack2=new Stack();
while(pHead1!=null){
stack1.push(pHead1);
pHead1=pHead1.next;
}
while(pHead2!=null){
stack2.push(pHead2);
pHead2=pHead2.next;
}
ListNode comNode=null;
while(!(stack1.isEmpty() || stack2.isEmpty())&&stack1.peek().val==stack2.pop().val){
comNode=stack1.pop();
}
return comNode;
}
}
解析
方法三: 利用HashMap的性質,把第一個連結串列的值儲存到map中,遍歷第二個連結串列,當第二個連結串列的key在第一個連結串列中,返回這個節點 |
import java.util.HashMap;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode current1 = pHead1;
ListNode current2 = pHead2;
HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>();
while (current1 != null) {
hashMap.put(current1, null);
current1 = current1.next;
}
while (current2 != null) {
if (hashMap.containsKey(current2))
return current2;
current2 = current2.next;
}
return null;
}
}
解析
方法四: 把兩個連結串列連起來,如把pHead1後面連pHead2,pHead2後面連pHead1,此時,兩個連結串列一樣長 用兩個指標掃描”兩個連結串列“,最終兩個指標到達 null 或者到達公共結點 eg:1 2 3 6 7 4 5 6 7 連起來之後變為: 1 2 3 6 7 4 5 6 7 4 5 6 7 1 2 3 6 7 兩個指標遍歷到6,找到相等的節點 |
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1!=p2){
p1 = (p1==null ? pHead2 : p1.next);
p2 = (p2==null ? pHead1 : p2.next);
}
return p1;
}
}