原题链接:
第一题:整数中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;
}
}