- 计算机语言和开发平台日新月异,但万变不离其宗的是那些算法和理论。
- 算法和理论永远是程序员最重要的内功,秃头也得学呀
记录部分 当时的思路或者学习其他优秀作者的思路,也帮自己重新梳理逻辑,争取加深印象,加油 (^-^)V
末尾0的个数
思路:计算1~n的因数 , 一共有多少个5。
举例100: n = 100/5 + 100/5/5 = 24
( 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 )
其中 25 50 75 100 都有2个因数为5
n/5 是计算因数包含5的数的个数,n /5 /5 是计算因数为25的个数,同样 n/5/5/5是计算因数为125的个数。
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int res = 0;
while(n/5>0){
res+=n/5;
n/=5;
}
System.out.println(res);
}
}
寻找丑数
滴滴中等笔试真题:
为了防止重复计算,较好的做法是将之前计算过的丑数记录下来,这样判断n 是否为丑数时,就只需要判断n%2==0 || n%3==0 || n%5==0
,如果成立则继续判断已经计算出来的 n/2 || n/3 || n/5
是否为丑数,就防止了重复计算;
这里用ArrayList做存储已经计算出的丑数。
import java.util.*;
public class Main{
public static void main(String args[]){
int n,cur=5;
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
if(n<=5){
System.out.println(n);
return;
}
List<Integer> list = new ArrayList();
list.add(0);
list.add(1);//1
list.add(1);//2
list.add(1);//3
list.add(1);//4
list.add(1);//5
while(n>5){
cur++;
if(cur%2==0 && list.get(cur/2)==1
|| cur%3==0 && list.get(cur/3)==1
||cur%5==0 && list.get(cur/5)==1)
{
list.add(1);
n--;
}
else{
list.add(0);
}
}
System.out.println(cur);
}
}
螺旋矩阵
没有特殊知识点,只要代码不写错,注意细节就好。
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
if(matrix == null) return res;
spiral(res,matrix,0,matrix[0].length-1,0,matrix.length-1);
return res;
}
public void spiral(List<Integer> res,int[][] matrix,int l1,int l2,int r1,int r2){
if(l1>l2 || r1>r2) return;
for (int i = l1; i <= l2 ; i++) {
res.add(matrix[r1][i]);
}
if(l1==l2&& r1==r2) return;
for (int i = r1+1; i <= r2-1; i++) {
res.add(matrix[i][l2]);
}
if(r1!=r2)
for (int i = l2; i >=l1 ; i--) {
res.add(matrix[r2][i]);
}
if(l1!=l2)
for (int i = r2-1; i >= r1+1; i--) {
res.add(matrix[i][l1]);
}
spiral(res,matrix,l1+1,l2-1,r1+1,r2-1);
}
}
括号生成
- 首先想到递归
- 可以n个
(
和 n 个)
的全排序,然后再剔除无效 (效率太低)- 他和全排序的区别:只有两种不同的符号、需要按照一定规律,不可右括号先行
- 可以用L 和 R 分别记录左右括号的剩余个数,他们的关系始终遵循L<=R,就不会出毛病!
- 开始敲代码
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
String current = ""; // 记录当前拼接情况,初始化为空,也可以初始化为"("
go(res,current,n,n);//开始递归
return res;
}
public void go(List<String> res,String cruuent ,int l,int r){
//出口
if(l==0&&r==0){
res.add(cruuent);
return;
}
//始终遵循L<=R,所以他们不是 l==r 就是 l<r
if(l==r){
go(res,cruuent+"(",l-1,r);
}else {
//如果还有左括号,就加入左括号
if(l>0)
go(res,cruuent+"(",l-1,r);
//加入右括号
go(res,cruuent+")",l,r-1);
}
}
电话号码的字母组合(全排序)
在看别人代码的时候,不要以为看懂你就会敲了
自己敲一遍下来,以后类似的题目,做起来也也可以得心应手~
思路
这个和全排序有相似之处,顺便把全排序做了吧~
(递归大法好)
按照深度优先的方式,保存所有可能的结果。
class Solution {
char [][] digitsLetter={{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'}
,{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}};
public List<String> letterCombinations(String digits) {
List<String> res=new ArrayList<>(); //存储结果集
if(digits.length()==0)return res;
char[] currentRes=new char[digits.length()];//存储可能的排序
letterComb(res,0,currentRes,digits);//递归
return res;
}
public void letterComb(List<String> res,int i,char[] currentRes,String digits){
boolean isLast=false;//判断是否是最后一个数字
if(i==digits.length()-1) isLast=true;
for (int j = 0; j < digitsLetter[digits.charAt(i)-50].length; j++) {
currentRes[i]=digitsLetter[digits.charAt(i)-50][j];
if(isLast){//最后一个数字,加入结果集
StringBuilder stringBuilder=new StringBuilder();
for (int k = 0; k <=i; k++) {
stringBuilder.append(currentRes[k]);
}
res.add(stringBuilder.toString());
}else//否则继续递归
letterComb(res,i+1,currentRes,digits);
}
}
}
全排序
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length == 0) {
return res;
}
permute(nums, 0, res);
return res;
}
private void permute(int[] nums, int i, List<List<Integer>> res) {
if (i == nums.length) {
ArrayList<Integer> tmp = new ArrayList<>();
for (int num : nums) {
tmp.add(num);
}
res.add(tmp);
return;
}
for (int j = i; j < nums.length; j++) {
swap(nums, i, j);
permute(nums, i + 1, res);
swap(nums, i, j);
}
}
private void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
四数之和
思路:在三数之和的基础上
我们已经做出来了三数之和,先指定一个数a,然后在定义左右指针寻找b,c。
那么四个数也可以这样做,指针i
指定a以后,指针k
每次指定d首先为最后一个数,然后指针l
指向a的后一个数,指针r
指向d的前一个数。
也就是在指针i
内部加一个指针k
循环,最内侧循环依然是l,r
:
注意:这时需要加一个去重条件,防止k指向大小相同的数。
while (k>i && nums[k - 1] == nums[k]) k--;
这样大体思路就完成了,来实现一下:
public List<List<Integer>> fourSum(int[] nums,int target) {
List<List<Integer>> res=new ArrayList<>();
if(nums.length==0)return res;
Arrays.sort(nums);
for (int i = 0; i < nums.length - 3; i++) {
for (int k = nums.length-1; k > i+2; k--) {
int target1 = target - nums[i]-nums[k];
int l = i + 1, r = k- 1;
if(nums[i]*2>target1 || nums[k]*2<target1)continue;
while (l < r) {
if (nums[l] + nums[r] == target1) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[l]);
list.add(nums[r]);
list.add(nums[k]);
res.add(list);
l++;
r--;
while (l < r && nums[l] == nums[l - 1]) l++;
while (r > l && nums[r] == nums[r + 1]) r--;
} else if (nums[l] + nums[r] > target1) {
r--;
} else {
l++;
}
}
while (k>i && nums[k - 1] == nums[k]) k--;
}
while (i + 1 < nums.length && nums[i + 1] == nums[i]) i++;
}
return res;
}
== 优化前执行结果==
优化
执行用时的误差还是比较小的,执行的结果还不是特别令人满意,我希望可以优化一下。优化无非希望他可以减少遍历不可能的答案,当已经定位了a
和d
,我们便可以得出接下来获得的最大和、最小和。
最大和a+d*3
<target ===那么l和r不再遍历
最小和a*3+d
>target ===那么l和r不再遍历
那么优化其实只要再l,r遍历之前加上方框内的判断条件:
其中target1
为target
减去a
和d
优化后执行结果
Z字形变换 (StringBuilder)
行数为3:
0 4 8
1 3 5 7 9
2 6
行数为4:
0 6 12
1 5 7 11
2 4 8 10
3 9
分析上面的两个例子,可以发现其实有规律可循。
String res="";//结果
int n=s.length();
//l为两个N之间的距离,是一个固定值,N的那条斜线距离两竖距离是根据rows变化的,用l1记录。
int l=2*numRows-2,l1=0,p=0;//p为s的指针
实现1: 用String存储拼接结果
public String convert1(String s, int numRows) {
String res="";
int n=s.length();
int l=2*numRows-2,l1=0,p=0;
while(p<n){
res+=s.charAt(p);
p+=l;
}
for (int i = 1; i <numRows-1 ; i++) {
l1+=2;
p=i;
while(p<n){
res+=s.charAt(p);
p+=l-l1;
if(p<n){
res+=s.charAt(p);
p+=l1;
}
}
}
p=numRows-1;
while(p<n){
res+=(s.charAt(p));
p+=l;
}
return res;
}
通过是没问题的
在res字符串中,完全是进行字符串拼接操作。
但是了解StringBuilder的同学肯定知道,String是final修饰不可变的,每次修改值都会创建新的对象,大大加重了计算负担,而Stringbuilder和StringBuffer是在原有的值上进行修改不用创建新对象引用,大大提高了效率。
实现2: 用StringBuilder:快了好几倍。
public String convert(String s, int numRows) {
if(numRows==1)return s;
StringBuilder res=new StringBuilder();
int n=s.length();
int l=2*numRows-2,l1=0,p=0;
while(p<n){
res.append(s.charAt(p));
p+=l;
}
for (int i = 1; i <numRows-1 ; i++) {
l1+=2;
p=i;
while(p<n){
res.append(s.charAt(p));
p+=l-l1;
if(p<n){
res.append(s.charAt(p));
p+=l1;
}
}
}
p=numRows-1;
while(p<n){
res.append(s.charAt(p));
p+=l;
}
return res.toString();
}
无重复字符的最长子串(滑动窗口)
第一思路:HashSet容器,以每个字符开头,遍历n-1次(字符串长度n),每次将元素加入容器,当有重复元素时喊停记录长度l,然后在清空容器,但是频繁的加入容器和清空消耗大量的时间。(打败全国5%的人T.T)
然后认识了一种叫滑动窗口法的方法:
定义两个指针,start和end,代表当前窗口的开始和结束位置,同样使用hashset,当窗口中出现重复的字符c且c的索引值>start时,start移动到容器中已有的c后面,没有重复时,end++,每次更新长度的最大值
从1000ms减到10ms,减少了频繁加入元素和清除容器的时间。这里参考下别人的优秀作业:
public int lengthOfLongestSubstring(String s) {
int res = 0;
int end=0,start=0;
Map<Character,Integer> map=new HashMap<>();
for(;end<s.length();end++){
if(map.containsKey(s.charAt(end))){
start=Math.max(map.get(s.charAt(end)),start);//从有重复的下一个位置继续找
}
map.put(s.charAt(end),end+1);//map每次更新
res=Math.max(res,end-start+1);//结果每次更新
}
return res;
}
三数之和
要求:a+b+c=0,且三元组不重复。
简单起见,假设他们是一个有序数组,从第一个数开始作为a,target=-a,之后寻找b和c,令左指针指向a的下一个数b,右指针指向最后一个数c,while(l<r)
:
- 如果
b+c==target
,加入结果集,两指针向中间靠拢,继续寻找答案。 - 如果
b+c<target
,左指针右移 - 如果
b+c>target
,右指针左移 - 内部去重:如下图,此时
nums[i]=-1,target=1,
指针的当前位置已经符合条件,那么r继续左移不查重的话会导致答案重复,所以需要内部去重
。
- 外部去重:如图,当第一个 i 指向第一个 -1,并且已经找到所有target=1的情况了,那么i就没有必要再指向第二三个-1了,所以需要外部去重。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res=new ArrayList<>();
if(nums.length==0)return res;
Arrays.sort(nums);
for (int i = 0; i < nums.length-2; i++) {
int target=0-nums[i];
int l=i+1,r=nums.length-1; //左指针和右指针
while (l<r){
if(nums[l]+nums[r]==target){//等于答案,加入res
List<Integer> list=new ArrayList<>();
list.add(nums[i]);
list.add(nums[l]);
list.add(nums[r]);
res.add(list);
l++;r--;
while (l<r&&nums[l]==nums[l-1]) l++;//内部去重
while (r>l&&nums[r]==nums[r+1]) r--;
}else if(nums[l]+nums[r]>target){//大于目标 ,右指针往左
r--;
}else l++; 小于目标 ,左指针往右
}
while (i+1<nums.length&&nums[i+1]==nums[i])i++;//外部去重
}
return res;
}
}
两数之和
官方提示:
第二个思路是,在不更改数组的情况下,我们可以以某种方式使用额外的空间吗? 像是散列表来加快搜索速度?
注意:
- 元素可能重复
输入:nums = [3,3], target = 6
输出:[0,1] - 这边了解一下取值范围,不会超过int的21亿多
2 <= nums.length <= 103
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
思路: 利用hashmap存储结构!遍历数组,每遍历一个元素nums[i]
,寻找与其匹配的t
( t = target - nums[i]) ,若存在hashmap.contains(t),则得出结论,否则,将num[i],i put 进 hashmap
public int[] twoSum(int[] nums, int target) {
int [] res = new int[2];
HashMap<Integer,Integer> hashMap = new HashMap();
for (int i = 0; i < nums.length; i++) {
int t = target - nums[i];
if (hashMap.containsKey(t)){
res[0] = hashMap.get(t);
res[1] = i;
return res;
}else {
hashMap.put(nums[i],i);
}
}
return res;
}
跳台阶【dp】
可以采用dp思想:可以采用以下例子,但是会有重复的计算,比如需要重复计算f3的值
f5 = f4 + f3
= (f4+ f3) + f3
=((f3+f2)+f3)+f3…
public int JumpFloor(int target) {
if(target == 1) {
return 1;
}
if(target == 2){
return 2;
}
return JumpFloor(target-1)+JumpFloor(target-2);
}
改进:将结果存储起来,下次需要直接获取。
public static int JumpFloor(int target) {
if(target <3) return target;
int []a = new int[target+1];
a[1] = 1;
a[2] = 2;
for(int i = 3;i<= target;i++){
a[i] = a[i-1]+a[i-2];
}
return a[target];
}