题解(01-10):link
题解(11-20):link
题解(21-30):link
题解(31-40):link
题解(41-50):link
题解(51-60): link
题解(61-67): link
我们将 f(n)
记为 1~n
这 n 个整数的十进制中 1 出现的次数,将 n 拆分成两个部分,最高一位的数字 high 和剩下位的数字 last,分别判断情况后将结果累加。
举两个栗子(也是两大种情况):
n = 1234 ——> high = 1, pow = 1000, last = 234
我们将数字 1234
分成两个部分,1 ~ 999
和 1000 ~ 1234
1 ~ 999
这个范围的 1
的个数为 f(pow - 1)
1000 ~ 1234
这个范围的 1
的个数又要分成两个部分
1
的个数(只看千位):这个个数就刚好是 234 + 1(last + 1)
234
中 1
出现的次数,即 f(234)->f(last)
f(pow - 1) + (last + 1) + f(last)
n = 3234,high = 3, pow = 1000, last = 234
我们将数字 3234
分成后面几种部分,1 ~ 999
和 1000 ~ 1999
和 2000 ~ 2999
和 3000 ~ 3234
1 ~ 999
这个范围的 1
的个数为 f(pow - 1)
1000 ~ 1999
这个范围 1 的个数需要分为两部分 :
1
的个数(只看千位):刚好就是 pow
1
的个数:即 999
中出现的 1
的个数,就是f(pow - 1)
2000 ~ 2999
出现的 1
的个数就是 f(pow - 1)
,因为最高位已经不是 1
了3000 ~ 3234
出现的 1
的个数就是 f(last)
,同样因为最高位已经不是 1
了pow + high * f(pow - 1) + f(last)
。因为这个 high
实际根据具体数字来获取class Solution {
public int countDigitOne(int n) {
return f(n);
}
private int f(int n) {
if (n <= 0) return 0;
// 把 n 转换成字符串,方便取
String s = String.valueOf(n);
// 获取最高位的数字
int high = s.charAt(0) - '0';
// 1299 -> pow = 1000
int pow = (int)Math.pow(10, s.length() - 1);
// last = 1299 - 1 * 1000 = 299
int last = n - high * pow;
// 如果高位为1,
if (high == 1) {
return f(pow - 1) + last + 1 + f(last);
} else {
return pow + f(pow - 1) * high + f(last);
}
}
}
题目描述: 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。
m,n
,组合的方式就两种, mn
或者 nm
,在这两个中找出最小的mn
和nm
,如果mn < nm
,那 m 应该在前;如果nm < mn
,那么 n 应该在前。因此,我们得到的排序规则如下:
mn > nm
,则 m > n
mn < nm
,则 n > m
mn = nm
,则 n = m
public class Solution {
public String PrintMinNumber(int [] numbers) {
String res = "";
if (numbers == null || numbers.length == 0) return res;
int N = numbers.length;
String[] str = new String[N];
// 将数字数字转化为字符串数据,方便后续操作
for (int i = 0;i < N;i ++)
str[i] = String.valueOf(numbers[i]);
// 按规则将str按照从小到大排序。比较规则就是 mn > nm 是否成立
Arrays.sort(str, new Comparator<String>() {
@Override
public int compare(String m, String n) {
String s1 = m + n, s2 = n + m;
return s1.compareTo(s2);
}
});
// 下面这句使用lambda表达式
// Arrays.sort(str, (m, n) -> ( (m + n).compareTo(n + m)));
for (String s : str)
res += s;
return res;
}
}
题目描述: 把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含质因子 7。 习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。
后面的丑数是由前面的丑数乘以 2、3、5 来获得的
参照牛客网的解答
import java.util.ArrayList;
public class Solution {
public int GetUglyNumber_Solution(int index) {
// 1-6 都是丑数
if (index < 7) return index;
ArrayList<Integer> list = new ArrayList<>();
// 第一个丑数是1
list.add(1);
// p1,p2,p3分别记录乘以2,3,5的索引
int p1 = 0, p2 = 0, p3 = 0;
// ArrayList 可以根据下标随机访问
while (list.size() < index) {
int m1 = list.get(p1) * 2;
int m2 = list.get(p2) * 3;
int m3 = list.get(p3) * 5;
// 要排序,所以每次选最小的出来加入结果队列
int min = Math.min(m1, Math.min(m2, m3));
list.add(min);
if (min == m1) p1 ++;
if (min == m2) p2 ++;
if (min == m3) p3 ++;
}
return list.get(index - 1);
}
}
还是使用一个额外的数组来存贮丑数
public class Solution {
public int GetUglyNumber_Solution(int index) {
if (index < 7) return index;
int[] res = new int[index];
// 第一个丑数是1
res[0] = 1;
// p1,p2,p3分别记录乘以2,3,5的索引
int p1 = 0, p2 = 0, p3 = 0;
// 因为 res 需要是有序的,所以每次只取乘以2或3或5中值的最小值
for (int i = 1;i < index;i ++) {
// 下一个丑数是由上一个丑数乘以2或3或5得来的,且需要有序,我们就取最小值
res[i] = Math.min(res[p1] * 2, Math.min(res[p2] * 3, res[p3] * 5));
if (res[i] == res[p1] * 2) p1 ++;
if (res[i] == res[p2] * 3) p2 ++;
if (res[i] == res[p3] * 5) p3 ++;
}
return res[index - 1];
}
}
题目描述: 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)
values
,在大于等于 0 的结果中找到最小值,即为答案public class Solution {
public int FirstNotRepeatingChar(String str) {
if (str.length() == 0) return -1;
HashMap<Character,Integer> map = new HashMap<>();
char[] arr = str.toCharArray();
// 遍历字符数组,存 char -> index 的映射关系
for (int i = 0;i < arr.length;i ++) {
if (!map.containsKey(arr[i]))
map.put(arr[i], i);
else
map.put(arr[i], -1);
}
int min = Integer.MAX_VALUE;
// 找到最小的结果
for (int value : map.values()) {
if (value >= 0)
min = Math.min(min, value);
}
return min;
}
}
A-Z(65-90),a-z(97-122)
中间有 6 个元素的其他字符,所以干脆将这几个字符位置算上,否则要分别判断是大写还是小写public class Solution {
public int FirstNotRepeatingChar(String str) {
int[] alpha = new int[58]; // 中间包括大写字母Z(90) - 小写字母a(97) 之间的其他字符
for (int i = 0;i < str.length();i ++) {
alpha[(int)str.charAt(i) - 'A'] ++;
}
for (int i = 0;i < str.length();i ++) {
if (alpha[(int)str.charAt(i) - 'A'] == 1) return i;
}
return -1;
}
}
描述见链接
res
的取值可能超过 int
范围,所以使用 long
来存储public class Solution {
// 结果可能超出 int 的范围
private long res;
private int[] aux;
public int InversePairs(int [] array) {
// 使用归并的思想
if (array == null || array.length <= 1) return -1;
aux = new int[array.length];
mergeHelper(array, 0, array.length - 1);
return (int)(res % 1000000007);
}
private void mergeHelper(int[] arr, int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeHelper(arr, left, mid);
mergeHelper(arr, mid + 1, right);
// 这个地方算是一个优化,如果前半部分的最大值已经小于后半部分的最小值了,
// 那么就没有必要再进行下面的操作,因为数组已经有序了
if (arr[mid] > arr[mid + 1])
merge(arr, left, mid, right);
}
// 一边归并,一边记录逆序对的数量。几乎和归并排序一样,只是输出的是res
private void merge(int[] arr, int left, int mid, int right) {
// 还是需要辅助数组来帮忙
for (int i = left;i <= right;i ++)
aux[i - left] = arr[i];
int i = left, j = mid + 1;
for (int k = left;k <= right;k ++) {
if (i > mid) {
arr[k] = aux[j++ - left];
} else if (j > right) {
arr[k] = aux[i++ - left];
} else if (aux[i - left] <= aux[j - left]) {
arr[k] = aux[i++ - left];
} else {
// 当aux[i - left] > aux[j - left]时,前半部分i后面的元素都大于j对应的元素
res += (mid - i + 1);
arr[k] = aux[j++ - left];
}
}
}
}
题目描述: 输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
l1.size() + l2.size() = l2.size() + l1.size()
l1
接到l2
时,计数器加一,反之也加一,一共就两次,如果超出两次,说明两个链表没有公共节点。public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
// 两条链表的长度加起来是相等的
if (pHead1 == null || pHead2 == null) return null;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
int count = 0;
while (p1 != p2 && count <= 2) {
p1 = p1.next;
p2 = p2.next;
if (p1 == null) {
// 接到对方链表上去。计数器加一
p1 = pHead2;
count ++;
}
if (p2 == null) {
// 接到对方链表上去,计数器加一
p2 = pHead1;
count ++;
}
}
// p1 != p2 是由于 count 超过 2 循环结束导致的
return p1 == p2 ? p1 : null;
}
}
题目描述: 统计一个数字在排序数组中出现的次数
public class Solution {
public int GetNumberOfK(int [] array , int k) {
// 边界条件
if (array == null || array.length == 0) return 0;
if (k < array[0] || k > array[array.length - 1]) return 0;
int count = 0;
for (int i = 0;i < array.length;i ++) {
if (k > array[i]) continue;
// 相等之后再不相等就可以退出循环了
else if (k == array[i]) {
while (i < array.length && k == array[i]) {
count ++;
i ++;
}
break;
}
}
return count;
}
}
public class Solution {
// 设计成全局变量,便于累加
private int res = 0;
public int GetNumberOfK(int [] array , int k) {
if (array == null || array.length == 0) return 0;
helper(array, k, 0, array.length - 1);
return res;
}
private void helper(int[] array, int k, int l, int r) {
if (l > r) return;
int mid = l + (r - l) / 2;
if (array[mid] < k) {
// k 在 mid 右边,去右边递归查找。左边同理
helper(array, k, mid + 1, r);
} else if (array[mid] > k) {
helper(array, k, l, mid - 1);
} else {
// 在相等的情况下左边可能还有值,右边也可能还有值,所以继续递归调用
res ++;
helper(array, k, l, mid - 1);
helper(array, k, mid + 1, r);
}
}
}
题目描述: 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
深度 = max(左孩子深度,右孩子深度) + 1
public class Solution {
public int TreeDepth(TreeNode root) {
// 很显然想使用递归,深度 = max(左孩子深度,右孩子深度) + 1
if (root == null) return 0;
// 递归终止条件,只有一个根节点,所以返回1
if (root.left == null && root.right == null) return 1;
int leftH = 0, rightH = 0;
// 左孩子深度
// if (root.left != null) 可以省略,因为函数开头保证了根节点为空返回0
leftH = TreeDepth(root.left);
// 右孩子深度
// if (root.right != null)
rightH = TreeDepth(root.right);
return Math.max(leftH, rightH) + 1;
}
}
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
// 任意一个节点左右两个子树的高度差不超过1
// 一边遍历节点,一边记录节点的高度
if (root == null) return true;
int res = getDepth(root);
if (res == -1)
return false;
return true;
}
private int getDepth(TreeNode root) {
if (root == null) return 0;
// 记录左子树高度,如果左子树非平衡二叉树,则可以返回左子树高度为-1
int left = getDepth(root.left);
if (left == -1) return left;
// 记录右子树高度,如果右子树非平衡二叉树,则可以返回右子树高度为-1
int right = getDepth(root.right);
if (right == -1) return right;
// 记非平衡二叉树高度为 -1
if (Math.abs(left - right) > 1) return -1;
// 左右子树都是平衡二叉树,则返回该树的高度
else return left > right ? left + 1 : right + 1;
}
}
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
int leftH = getHeight(root.left);
int rightH = getHeight(root.right);
// 先判断根节点的左右是否满足
if (Math.abs(leftH - rightH) > 1) {
return false;
}
// 再递归判断左孩子和右孩子是不是平衡二叉树
return isBalanced(root.left) && isBalanced(root.right);
}
// 获得树的高度,用来判断是不是平衡二叉树,用到了 38 求二叉树的高度
private int getHeight(TreeNode root) {
if (root == null) return 0;
int left = getHeight(root.left);
int right = getHeight(root.right);
return Math.max(left, right) + 1;
}
}
题目描述: 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
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.containsKey(array[i])) map.put(array[i], 1);
else map.remove(array[i]);
}
int[] aux = new int[2];
int size = 0;
for (int i : map.keySet())
aux[size ++] = i;
num1[0] = aux[0];
num2[0] = aux[1];
}
}
n ^ n = 0
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
/*
思路:数组中的元素先依次异或,相同为0,则得到的是两个只出现一次的数的异或结果
对于得到的异或结果,找到其第一个为1的位
该位为1,说明两个只出现一次的数该位不同,所以按照该位是0还是1将数组分成两部分
这样,出现两次的数字都会分到同一个部分,而两个只出现一次的数正好被分开,再各自异或可得结果
*/
if (array == null || array.length < 2) return;
int res = 0;
for (int num : array)
res ^= num; // 异或得到的结果为只出现一次的两个元素的异或,必定至少有一位为1
int index = 0;
// 右移32是因为int就是32位的,找到从右向左第一个为1的位index
for ( ;index < 32; index ++) {
if (((res >> index) & 1) == 1)
break;
}
// 根据index位为1还是0,将元素分为两组,那么仅有的两个唯一元素就被分开了
for (int num : array) {
if (((num >> index) & 1) == 1)
num1[0] ^= num;
else
num2[0] ^= num;
}
}
}