其他算法
打印从1到最大的n位数
- 判断每个数字的长度
主要在于判断数字的长度,数字转成字符串的方式
- “” + a
- Double.toString(double)
- StringBuilder.toString()
- while循环
1
2
3
4
5
6
7private int getLength(int n) {
int i;
for (i=0; n>0; i++) {
n /= 10;
}
return i;
}
- 求出最大长度
使用以上判断长度的方法都超时,应该直接使用Math.pow(10, n) - 1求出最大长度
不过要强制类型转换,精度只能自动升级,无法自动降级
然后创建该长度的数组,逐一赋值,不过要从1开始,比索引值大1
1 | public int[] printNumbers (int n) { |
数字序列中某一位的数字
StringBuilder计数
利用StringBuilder从0开始添加,不停地判断stringbuilder的长度,是否达到或超过了n,超过了返回charAt(n),使用(int)charAt(n) - (int)’0’转成数字累加计数
每增加一个数字,计算它的长度,并累加进总长度,当加到i时,总长度index(注意有第0位,其实第多少位是索引)超过给定的长度n
index = index - getLength(i) 得到还没有加i时的长度,n - index 得到差几位
调整数组顺序奇数位于偶数前面(二)
- 新建数组
遍历原来的数组,根据奇偶判断加入到新数组的头部与尾部
1 | public int[] reOrderArrayTwo (int[] array) { |
- 原位操作
左边循环找到偶数,右边循环找到奇数,然后交换
1 | public int[] reOrderArrayTwo (int[] array) { |
构建乘积数组
- 使用除法
计算数组A的总乘积multi,B[i] = multi / A[i],但是不排除A[i] 为 0的情况,这时候就需要重新计算除该元素外的其他元素的乘积,代码反而多了起来
1 | public int[] multiply(int[] A) { |
- 不用除法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public int[] multiply(int[] A) {
int multi = 1;
int length = A.length;
int[] B = new int[length];
for (int i=0; i<length; i++) {
B[i] = this.getMultiExcept(A, i);
}
return B;
}
public int getMultiExcept(int[] A, int index) {
int multi = 1;
for (int i=0; i<A.length; i++) {
if (i == index) {
continue;
}
multi *= A[i];
}
return multi;
} - 更优算法
1 | public int[] multiply(int[] A) { |
第一个只出现一次的字符
- 利用map
1
2
3
4
5
6
7
8
9
10
11
12
13public int FirstNotRepeatingChar(String str) {
char[] chars = str.toCharArray();
Map<Character, Integer> map = new HashMap<>();
for (int i=0; i<chars.length; i++) {
map.put(chars[i], map.getOrDefault(chars[i], 0)+1);
}
for (int i=0; i<chars.length; i++) {
if (map.get(chars[i]) == 1) {
return i;
}
}
return -1;
}
调整数组顺序使奇数位于偶数前面(一)
- 写错了[2, 4, 6, 5, 7] 会输出 [5, 7, 6, 2, 4] 没有保证相对顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public int[] reOrderArray (int[] array) {
// write code here
for (int i=0; i<array.length-1; i++) {
if (array[i] % 2 == 1) {
continue;
}
// 如果array[i]是偶数,和后面的第一个奇数交换
for (int j=i+1; j<array.length; j++) {
if (array[j] % 2 == 1) {
this.swap(array, i, j);
break;
}
}
}
return array;
}
private void swap(int[] array, int src, int des) {
int temp = array[src];
array[src] = array[des];
array[des] = temp;
} - 使用奇数、偶数列表保存结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public int[] reOrderArray (int[] array) {
// write code here
List<Integer> odd = new ArrayList<>();
List<Integer> even = new ArrayList<>();
int length = array.length;
for (int i=0; i<length; i++) {
if (array[i] % 2 == 1) {
odd.add(array[i]);
} else {
even.add(array[i]);
}
}
int[] result = new int[length];
for (int i=0; i<odd.size(); i++) {
result[i] = odd.get(i);
}
for (int i=0; i<even.size(); i++) {
result[i + odd.size()] = even.get(i);
}
return result;
} - 原位修改
遍历数组,找到奇数,插入到已有的奇数后面
1 | public int[] reOrderArray (int[] array) { |
剪绳子
- 贪心
每段相等时乘积最大,求导发现每段等于e时,乘积最大,e取不到,取3时乘积最大
1 | public int cutRope (int n) { |
- 动态规划
1
2
3
4
5
6
71. dp[i] 长度为i的绳子最大乘积
2. dp[i] = max(1 * dp[i-1], 2 * dp[i-2], ..., j*dp[i-j]) 把绳子分成两部分,没剪的部分(j)和剪的部分(dp[i-j])
3. dp[0] = 0 dp[1] = 1 dp[2] = 2 dp[3] = 3 dp[4] = 4
4. 前后遍历
5. 6
dp[i] 0 1 2 3 4 6 9
1 | public int cutRope (int n) { |
剪绳子进阶版本
- 快速乘法与快速幂
要求时间复杂度是O(logn),之前使用递归的方式不断乘以3时间复杂度是O(n)。可以发现不断乘以3的运算是幂运算,而快速幂时间复杂度正好是O(logn)
可以通过 number % 3 的结果提前知道3的幂运算次数,从而使用快速幂运算
numer % 3 == 0 幂运算次数 number / 3
number % 3 == 1 需要减去一个3,和剩下的1凑成4 幂运算次数 number / 3 - 1
number % 3 == 2 幂运算次数 number / 3
由于number 数字特别大,在快速幂中,result * result 可能会超过long的最大值
在所以需要自己定义乘法操作,在防止溢出,这也是题目让最后结果取模一个数字的原因
可以在快速乘法中取模给定的数字,防止溢出
快速乘法(快速乘法并不快,而是用加法代替乘法,在求解的过程中取模,防止溢出)
a * b = a * (1 * 2^(n-1) + 0 * 2^(n-2) + … + 1 * 2^(2) + 0 * 2^(1) + 1 * 2^(0))
a * b = a * 2^(n-1) * 1 + a * 2 (n - 2) * 0 + …
可以发现每一项都是 a * 二进制中的某一位 * 2的幂 可以通过位运算得到2的幂与二进制中的某一位,a * 2的幂可以通过移位实现,左移n位,相当于乘以2的n次幂
进阶版绳子的最大长度从60扩大为了10^14,在快速幂result * result时可能溢出,需要使用快速乘法,在函数中取模运算
1 | private long mod = 998244353; |
注意到给出的mod值998244353,即使这两个值相乘,是18为数,long最大值是19位,没有溢出所以自定义乘法可以简单点,先x y取模,最后的结果再取模,肯定不会溢出
1 | private long mod = 998244353; |
数组中出现超过一半的数字
1 | public int MoreThanHalfNum_Solution(int [] array) { |
数组排列成最小的数
- 回溯 内存溢出
相同长度的字符串数字,字典顺序越小,数字的值越小
可以找到数组的全排列,每一种排列都变成相同长度的字符串,然后根据字典顺序找到最小值,因为数字根大,超过了整型的范围,只能变成字符串,比较大小
由于number很长,当number为10时,会产生10!个排列方式,result存不下,内存溢出
1 | private List<String> result = new ArrayList<>(); |
- 重新定义大小关系
定义新的大小关系,比较字符串 num1num2 与 num2num1字典顺序(相同长度的字符串数字,字典顺序越小,数字的值越小),如果num1num2字典顺序小,说明num1应该在前面;如果num2num1字典顺序小,说明num2应该在前面
1 | private List<String> result = new ArrayList<>(); |
丑数
- 直接方法 超时
从2开始往后查找,如果是丑数,丑数的数量增加1,直到丑数的数量等于index
丑数的判断方法判断能否被2 3 5 整除,如果能整除,就循环除以2 3 5,如果最后只剩下1,说明被2 3 5 除尽了,是丑数,否则不是丑数
1 | public int GetUglyNumber_Solution(int index) { |
- 优化 超时
直接取模 2 3 5下降太慢,丑数也是235的乘积组合,取模已有的丑数,可以下降快点,但还是超时
1 |
|
- 构造丑数
用已经构造的丑数乘以 2 3 5,如果被选中了,对应指针就向后移动一位
1 | public int GetUglyNumber_Solution(int index) { |
和为S的连续序列
- 滑动窗口
滑动窗口就是在遍历过程中,动态地排除不可能地情况
当滑动窗口内的和比sum大的时候,以当前left作为开始,以right以及right后面作为结束的序列都排除了,所以left左移
当滑动窗口内的和比sum小的时候,以right作为结束,left以及left后面作为开始的序列都排除了,所以right右移
1 | public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) { |
和为S的两个数
想使用集合,contains 某个数的时候快一些
set 错误****没考虑相同数字的数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
ArrayList<Integer> result = new ArrayList<>();
Set<Integer> set = new HashSet<>();
for (int i=0; i<array.length; i++) {
set.add(array[i]);
}
for (int i=0; i<array.length && array[i]<sum; i++) {
//
if (set.contains(sum - array[i])) {
result.add(array[i]);
result.add(sum - array[i]);
break;
}
}
return result;
}set 处理相同数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
ArrayList<Integer> result = new ArrayList<>();
Set<Integer> set = new HashSet<>();
for (int i=0; i<array.length; i++) {
set.add(array[i]);
}
for (int i=0; i<array.length && array[i]<sum; i++) {
// 当array[i] 与 sum-array[i]相同的时候,判断该数字是否出现了超过两次
if (set.contains(sum - array[i]) && array[i] != sum - array[i] || array[i] == sum - array[i] && this.moreThanTwice(array, array[i])) {
result.add(array[i]);
result.add(sum - array[i]);
break;
}
}
return result;
}
private boolean moreThanTwice(int[] array, int n) {
int counter = 0;
for (int i=0; i<array.length; i++) {
if (array[i] == n) {
counter++;
if (counter == 2) {
return true;
}
}
}
return false;
}set优化,无需处理相同数字,边判断边加入set中,sum-array[i] 如果在集合中,就返回,如果不在就把array[i]加入到set中,这样,如果sum-array[i]与array[i]相同,也是因为之前出现过array[i],当前array[i]还没有加入到set中,这种方法不好想到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> result = new ArrayList<Integer>();
Set<Integer> set = new HashSet<>();
for (int i=0; i<array.length; i++) {
// set记录之前出现过的数字,array[i]肯定在当前数组中,不用判断,只用
// 判断之前的数字中是否有temp即可
int temp = sum - array[i];
if (set.contains(temp)) {
result.add(temp);
result.add(array[i]);
break;
} else {
set.add(array[i]);
}
}
return result;
}map
使用map记录出现的次数,如果array[i] 与 sum-array[i]相同,直接查看出现的次数是否超过2
1 | public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) { |
双指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> result = new ArrayList<Integer>();
int left = 0;
int right = array.length - 1;
while (left < right) {
int n1 = array[left];
int n2 = array[right];
if (sum == n1 + n2) {
result.add(n1);
result.add(n2);
break;
} else if (sum > n1 + n2) {
// 与最大的数n2相加都不够sum,说明n1太小,与n1的所有组合情况都被排除
left++;
} else {
// sum < n1 + n2 与最小的n1相加都超过sum,说明n2太大,与n2的所有组合情况都被排除
right--;
}
}
return result;
}直接方法
从特殊到一般,假如最初的数字序列为0 1 2 3 4 m=5
报m-1=4的小孩应该删除,也就是 m - 1 % n,可以从m-1<n的情况中总结出规律,应该用
m - 1 % n,而不是 m % n
取模运算的意义x % y一种理解方式是,[0, x-1]数字顺序排列,每行y个数,排成方阵,最后一个数x所在的列数,就是取模的结果
0 123 … y-1
0 1 2 3 … y-1
y y+1 y+2 y+3 … 2y-1
2y …
x-2 x-1 x
这种理解方式恰好于报数的意义一致,[0, m-1]报数,m-1 % n 运算得到的结果就是报m-1这个数字小孩的编号,删除这个小孩,后面的小孩补充过来
由于从下一个小孩接着数,m-1 % n表示从头开始数,还应该加上startIndex(也就是刚删除的index),表示多数startIndex个数,然后再从startIndex从0往后数一直到m-1
1 | public int LastRemaining_Solution(int n, int m) { |
- 数学推导+递归,太抽象了,没看懂
1 | public class Solution { |
字符流中第一个不重复的字符
- map list
map记录出现次数
list记录插入顺序,找到第一个次数是1的字符返回
1 | //Insert one char from stringstream |
- map queue
map记录出现次数
queue记录出现顺序,另外,字母出现次数超过1次就删除,后续就不用遍历判断它了
1 | //Insert one char from stringstream |
左旋转字符串
- 直接方法
观察发现就是把左边的一部分拼接到右边的末尾,顺序不变
而且当左移超过字符串长度之后,取模之后,在左移,结果一样
1 | public String LeftRotateString(String str,int n) { |
1出现的次数
- 逐个数,累加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public int NumberOf1Between1AndN_Solution(int n) {
int result = 0;
for (int i=1; i<=n; i++) {
result += this.countNumber(i);
}
return result;
}
// 循环 模10 除以10的方式提取各个位的数字
private int countNumber(int n) {
int counter = 0;
while(n != 0) {
if (n % 10 == 1) {
counter++;
}
n /= 10;
}
return counter;
}
// 变成字符串逐位查看是否是1,用字符串比较慢
private int countNumberByString(int n) {
String temp = "" + n;
int counter = 0;
for (char value : temp.toCharArray()) {
if (value == '1') {
counter++;
}
}
return counter;
} - 找规律,直接计算1
https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/mian-shi-ti-43-1n-zheng-shu-zhong-1-chu-xian-de-2/
按位分类讨论,计算每一位1出现的次数,然后相加
1 | public int NumberOf1Between1AndN_Solution(int n) { |