文章目录
31. 整数中1出现的次数
题目描述
求出1 ~ 13的整数中1出现的次数,并算出100 ~ 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数).
解题思路:
方法1:递归
f(n)为1-n范围内,这n个数中1出现的次数
首先将n分为两种情况:最高位为1(如1234)和最高位不为1(如3234)
情况一:当最高位为1时(以1234为例):
①:将n分为两部分,最高位单独拿出来为1,定义为high;剩下的部分为234,定义为last;
②:获取到最高位的分位,为千分位,即定义pow=1000;
③将数据n定义两个范围:
第一个范围:1-999范围内,1的个数为f(pow-1);
第二个范围:1000-1234范围内:
1)首先只考虑千分位是1的个数:也就是1000、1001、1002…1234,即234+1,转换下为: last+1
2)其次考虑其他位数上的1的个数为:f(last)。
综上所述:情况一中1出现的次数为: f(pow-1)+last+1+f(last)
情况二:当最高为不为1时(以3234为例):
①:1-999范围内,1的个数依然为:f(pow-1);
②:1000-1999范围内:
1)只考虑千分位是1的个数为:1000、1001、1002…1999即1000个也就是pow;
2)其他位数上1的数量为:f(pow-1)
③:2000-2999范围内1的个数:f(pow-1),注意这里的千分位是2,所以不要考虑分两种情况
④:3000-3234范围内1的个数:f(last)
综上所述:情况二中1出现的次数为:pow + high*f(pow-1) + f(last);
方法2: 利用StringBuilder,将每个数字追加到字符串中,进而统计1出现的次数。
public class Solution {
public int NumberOf1Between1AndN_Solution(int n){
//方法1:递归 划分成不同的段
if(n <= 0){
return 0;
}
String s = String.valueOf(n);
int high = s.charAt(0) - '0';
int pow = (int)Math.pow(10, s.length()-1);
int last = n - high*pow;
if(high == 1){
return NumberOf1Between1AndN_Solution(pow-1) + last + 1
+ NumberOf1Between1AndN_Solution(last);
}else{
return high * NumberOf1Between1AndN_Solution(pow-1) + pow
+ NumberOf1Between1AndN_Solution(last);
}
// //方法2:将数字转换成字符串
// StringBuilder sb = new StringBuilder();
// for(int i = 1; i < n+1; i++){
// sb.append(i);
// }
// int count = 0;
// for(int i = 0; i < sb.length(); i++){
// if(sb.charAt(i) == '1')
// count++;
// }
// return count;
}
}
32. 把数组排成最小的数
题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
解题思路:
比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2 和s2+s1哪个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。
import java.util.ArrayList;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if(numbers == null || numbers.length == 0)
return "";
//数字字符串a+b>b+a说明b是小于a的,b应该位于a前
for(int i = 0; i < numbers.length; i++){
for(int j = i+1; j < numbers.length; j++){
String str1 = numbers[i] + String.valueOf(numbers[j]);
String str2 = numbers[j] + String.valueOf(numbers[i]);
if(str1.compareTo(str2) > 0){
int temp = numbers[j];
numbers[j] = numbers[i];
numbers[i] = temp;
}
}
}
String str = new String("");
for(int i = 0; i < numbers.length; i++){
str = str + numbers[i];
}
return str;
}
}
33. 丑数
题目描述
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
解题思路:
丑数=2^x × 3^y × 5^z ,所以只需要把得到的丑数不断地乘以2、3、5之后并放入他们应该放置的位置即可,而此题的难点就在于如何有序的放在合适的位置。核心思想也是丑数乘以丑数得到丑数,只不过在找寻下一个丑数的时候并不是暴力的,而是确定了下一个丑数出现的范围,然后再在这个范围中取最小的即可。
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index <= 0)
return 0;
//初始化三个指向三个潜在成为最小丑数的位置
int p2 = 0, p3 = 0, p5= 0;
int[] result = new int[index];
result[0] = 1;
//一个丑数乘以2或者3或者5还是丑数
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++;
}else if(result[i] == result[p3]*3){
p3++;
}else if(result[i] == result[p5]*5){
p5++;
}
//下面语句容易忽略
if(result[i] == result[i-1])
i--;
}
return result[index-1];
}
}
34. 第一个只出现一次的字符位置
题目描述
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
解题思路:
方法1:类Hash方法,采用哈希思想。
遍历一遍字符串,统计每个字符出现的次数。然后再遍历一遍字符串,找出答案
方法2:: 利用HashMap
先创建一个HashMap,然后遍历整个字符串一遍,记录下每个字符出现的次数;
再次遍历整个字符串,根据我们前面存储的hashmap找哪个字符只出现过一次,直接返回他的位置
import java.util.HashMap;
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str == null || str.length() == 0)
return -1;
// //方法1
// //用一个类似哈希的东西来存储字符出现的次数
// int[] count = new int[256];
// for(int i=0; i<str.length(); i++){
// count[str.charAt(i)]++;
// }
// for(int i = 0; i < str.length(); i++){
// if(count[str.charAt(i)] == 1){
// return i;
// }
// }
// return -1;
//方法2:利用HashMap
HashMap<Character, Integer> map = new HashMap<>();
for(int i = 0; i < str.length(); i++){
if(map.get(str.charAt(i)) == null){
map.put(str.charAt(i), 1);
}else{
map.put(str.charAt(i), map.get(str.charAt(i))+1);
}
}
for(int i = 0; i < str.length(); i++){
if(map.get(str.charAt(i)) == 1){
return i;
}
}
return -1;
}
}
35. 数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
示例1
输入
[1,2,3,4,5,6,7,0]
返回值
7
解题思路:利用归并排序的思想
分治的思想,将数组不断一分为二,直到数组中只有两个元素,统计逆序对个数。然后对相邻的两个子数组进行合并,由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。在合并的时候也要计算组间的逆序对个数。
逆序对的总数 = 左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量
整个过程是一个归并排序的算法。
public class Solution {
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);
}
//二路归并 合并
public void MergeOne(int[] array, int start, int mid, int end){
int i = start;
int j = mid + 1;
int k = 0;
int[] temp = new int[end-start+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++];
}
//把排好序的temp移动到array
for(int t = 0; t < k; t++){
array[start+t] = temp[t];
}
}
public int InversePairs(int [] array) {
int[] temp = new int[array.length];
MergeSort(array, 0, array.length-1);
return cnt;
}
}
36. 两个链表的第一个公共结点
题目描述
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
解题思路:双指针法
创建两个指针p1和p2,分别指向两个链表的头结点,然后依次往后遍历。如果某个指针到达末尾,则将该指针指向另一个链表的头结点;如果两个指针所指的节点相同,则循环结束,返回当前指针指向的节点。比如两个链表分别为:1->3->4->5->6和2->7->8->9->5->6。短链表的指针p1会先到达尾部,然后重新指向长链表头部,当长链表的指针p2到达尾部时,重新指向短链表头部,此时p1在长链表中已经多走了k步(k为两个链表的长度差值),p1和p2位于同一起跑线,往后遍历找到相同节点即可。其实该方法主要就是用链表循环的方式替代了长链表指针先走k步这一步骤。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1 == null || pHead2 == null )
return null;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1 != p2){
p1 = p1 == null ? pHead2 : p1.next;
p2 = p2 == null ? pHead1 : p2.next;
}
return p1;
}
}
37. 数组在排序数组中出现的次数
题目描述
统计一个数字在升序数组中出现的次数。
解题思路:二分查找
一种投机取巧的思想:因为 array 中都是整数,利用二分法搜索 k-0.5 和 k+0.5 这两个数应该插入的位置,然后相减即可。注意是start <= end
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array.length == 0 || array == null){
return 0;
}
int first = binarySearch(array, k-0.5);
int last = binarySearch(array, k+0.5);
return last - first;
}
private int binarySearch(int[] array, double k){
int low = 0;
int high = array.length - 1;
while(low <= high){
int mid = (low + high)/2;
if(array[mid] < k){
low = mid + 1;
}else if (array[mid] > k){
high = mid - 1;
}else{
return low;
}
}
return low;//return low或者high都可以
}
}
38. 二叉树的深度
题目描述
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
解题思路:
方法1:递归
传入某节点,调用该方法,返回的应该是以传入节点为根节点的树的深度,而树的深度,肯定和左右子树深度有关,所以进入这个方法后,就包含了左右子树的深度(而要得到左右子树的深度,肯定又是以左右子节点为根节点,再次调用该方法深度获取的,因此此时进行递归),并且还有由一个左右深度比较的过程,然后取较大值,这个较大值就是该节点左右子树深度较深的值,以该值+1作为返回值,就是该节点的深度。
方法2:层次遍历
求最大深度,可用队列。因为要满足先进先出的特性。
借助队列,对二叉树进行层次遍历;
在层次遍历的过程中,每次当队列中某一层的节点出队完成后,高度+1;
关键点:判别队列中某一层节点出队完成的标准是什么?
在出队之前,此时队列中记录的只有某一层节点,所以队列的大小就是某一层节点的个数。当此个数减到0的时候,则说明该层节点全部出队完成
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
public int TreeDepth(TreeNode root) {
// //递归
// if(root == null){
// return 0;
// }
// int left = TreeDepth(root.left);
// int right = TreeDepth(root.right);
// return Math.max(left, right)+1;
//非递归——层次遍历
if(root == null){
return 0;
}
Queue<TreeNode> queue = new LinkedList();
queue.add(root);
int high = 0;
int size;//用size控制high增长的次数和时机(同一层的元素没有完全退出队列的时候high不可以增加)
TreeNode node;
while(queue.size() != 0){
size = queue.size();//队列长度
while(size != 0){
node = queue.poll();//从队列头部删除一个元素
if(node.left != null)
queue.add(node.left);
if(node.right != null)
queue.add(node.right);
size--;//当size==0时说明同一层的元素遍历完成
}
high++;
}
return high;
}
}
39. 平衡二叉树
题目描述
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树。
解题思路:
平衡二叉树定义:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
根据定义,我们只需要后序遍历此树,从树的叶子节点开始计算高度,只要有一个子树不满足定义就返回-1,如果满足继续向上计算高度。
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
return depth(root) != -1;
}
public int depth(TreeNode root){
if(root == null)
return 0;
int ldept = depth(root.left);
if(ldept == -1)
return -1;
int rdept = depth(root.right);
if(rdept == -1)
return -1;
int sub = Math.abs(ldept - rdept);
if(sub > 1)
return -1;
return Math.max(ldept, rdept)+1;
}
}
40. 数组中只出现一次的数字
题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
解题思路:哈希法
遍历一遍数组,用map记录出现的次数,然后再遍历一遍数组,找出出现1次的数字。
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Solution {
public void FindNumsAppearOnce(int [] array, int num1[] , int num2[]) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < array.length; i++){
if(map.get(array[i]) == null){
map.put(array[i], 1);
}else{
map.put(array[i], map.get(array[i])+1);
}
}
int count = 1;
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<Integer, Integer> entry = iterator.next();
if(entry.getValue() == 1){
if(count == 1){
num1[0] = entry.getKey();
count++;
}else{
num2[0] = entry.getKey();
break;
}
}
}
}
}