算法笔记 From Now To My Death
- 初级算法
- 中级算法
-
-
- 2、两数相加【预先指针】
- 3、无重复字符的最长子串【滑动窗口】
- 5、最长回文子串【动态规划】
- 6、Z字形变换
- 8、字符串转整数【模拟C/C++中的atoi函数】
- 11、盛水最多的容器【双层for-->双指针优化】
- 15、三数之和【排序+双指针】
- 16、最接近的三数之和【排序+双指针】
- 17、电话号码的数字组合【回溯递归】
- 18、四数之和【排序+双指针】
- 19、删除链表的倒数第n个节点【巧妙的双指针】
- 20、有效的括号【栈的使用】
- 21、合并两个有序链表【递归】***
- 22、括号生成【深度优先遍历 + 回溯递归】
- 24、两两交换链表中的节点【回溯递归】
- 29、两数相除【移位运算】
- 31、下一个排列
- 33、搜索旋转排序数组
- 34、在排序数组中找出目标元素第一个和最后一个出现的位置。
- 36、有效数独
- 38、外观数列【递归】
- 300、最长递增子序列【动态规划】
-
- 高级算法
此篇以完结:为了抓住面试重点:转向剑指Offer每日一题系列。Click me forward to new article
前言
以前当兵的时候,每次搞30公里强行军都很累。于是我会在50斤的背囊上写着:行百里者半九十——>靠着这句话,即使腿抽筋着,我都能坚持自己完成下来。
现在,我同样用这句话来激励自己,天下没有难学的技术,只有半途而废的人。不要求我一定能成为技术大牛(毕竟这需要一定的天赋和机遇)
旦求无愧于自己。平凡而不平庸即可。
比昨天的自己更好一点,比明天的自己更差一点

初级算法
1、两数之和
给定一个数组nums,和一个整数目标值target。从数组中找出两个元素,他们的和 = target。
返回对应两个元素的数组下标。
public int[] twoSum(int[] nums, int target){
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length(); i ++){
// 利用 【Map集合的containsKey API】
// 逆向思维: target = key + nums[i]
// ===> map.key = target - nums[i]时 返回结果!
if(map.containsKey(target - nums[i])){
return new int[]{
map.get(target - nums[i]), i};
}
}
map.put(nums[i],i);
}
return null;
如果用双层for循环固然简单,但是时间复杂度为O(n^2)
解题思路:

7、整数反转
给你一个32位的有符号整数x,返回将x中的数字反转后端结果。
如果反转后的整数超过32位的有符号整数的范围[-2^31, 2^31 - 1]——> 返回 0
public int reverse(int x) {
32位整数范围是:-2147483648 ~ 2147483647
为快速判断:只要将反转后的值,与最大值的最后两个数:十位 / 个位 进行比较即可
1、当反转后的值 大于十位之前的值时:无论它的各位数是几,都是越界的:214748365* 与 2147483647
2、当反转后的值 等于十位之前的值时:判断其个位 是否>最大值的个位:214748364* 与 2147483647
int res = 0; // 初始 0
while(x!=0) {
每次摘下当前 x 的个位
int tmp = x%10;
当摘下->放入 执行到214748364共9位时,即可进行判断
(如果全部反转完再判断,则会抛出异常)
因此,反转到最后2个(十位)时,如果大于了最大值的十位,则没必要比较个位了、
如果反转到十位时,发现和最大值的十位及其之前的数都相等。则需要再反转一次:比较个位
//判断是否 大于 最大32位整数
if (res>214748364 || (res==214748364 && tmp>7)) {
return 0;
}
//判断是否 小于 最小32位整数
if (res<-214748364 || (res==-214748364 && tmp<-8)) {
return 0;
}
// res:当前这一步while反转后的值
res = res*10 + tmp; 将当前 x 的个位,放入反转后的值的个位中
// 由于 原值 x 的末尾数字已经被取走:放入res中了。
// 因此 原值x 就少扣除末尾的那一位。
x /= 10;
}
return res;
}
9、回文数
给你一个整数x,如果x是一个回文整数,返回true。否则返回false。
eg:123不是回文。121是回文
直接StringBuilder.reverse => 但是这样要额外创建对象、并且反转整个字符串来比较
实际上:只要反转x的前半段,然后与后半段比较即可、如11222211; 反转1122—>2211 == 后半段!
private static boolean test(int x){
if (x <= 0 || ( x % 10 == 0 && x != 0)){
return false;
}
int revertedNum = 0;
// 通过 % 和 /的方式,达到string字符串remove的效果
// eg: 1122332211 ==> 每次从末尾摘除一位,赋给revertedNum后,x就要扣除一位
// 最后 x = 11223 == revertedNum = 11223 退出循环!
while (x > revertedNum){
// 反转后的数 = 上次反转的数 * 10 + 本次 x 的末尾摘下的个位
revertedNum = revertedNum * 10 + x % 10;
// 本次x被摘下了个位,于是就x剩下 x / 10;
// 这样只需要摘除x的一半长度时,即可判断是否为回文数
x = x / 10;
}
// 如果x是奇数:那么退出循环的结果会是 x=1122 revertedNum=11223
// 所以x == revertedNum / 10 时也为true;
return x == revertedNum || x == revertedNum / 10;
}
14、最长公共前缀
寻找一个字符串数组中的最长公共前缀,不存在则返回""
private static String test(String[] arr){
if (arr.length == 0){
return "";
}else if (arr.length == 1){
return arr[0];
}
String commonString = "";
String first = arr[0];
int flag = 0;
for (int i = 0; i < first.length(); i++) {
for (int j = 1; j < arr.length; j++) {
if (!arr[j].startsWith(first.substring(0, i))){
flag = 1;
break;
}
}
if (flag == 1){
break;
}
commonString = first.substring(0, i);
}
return commonString;
}
27、移除元素【拷贝复制】
给你一个整数数组、和一个目标值val。移除该数组中所有值等于val的元素。返回移除后的长度。
要求:原地修改数组、不使用额外的空间。
public static void main(String[] args) {
int[] nums = {
1,2,3,4,5,6};
System.out.println(removeElement(nums,5));
}
private static int removeElement(int[] nums, int val){
int result = 0;
for (int i = 0; i < nums.length; i++) {
//在原数组上:发现与val相同的元素,则跳过
// 与val不同的元素,则放入数组前面,保存下来。并且长度++
if (nums[i] != val){
nums[result] = nums[i];
result++;
}
}
return result;
}
28、实现strStr()【双指针】
给你两个字符串haystack和needle,请你再haystack字符串中找出needle字符串出现的第一个位置
如果不存在,则返回 -1; 当needle字符串为空时,应该返回0;
这与C语言定义的strStr()函数以及Java定义的indexOf()函数相当
双指针在数组遍历中非常非常地常见
private static int indexOf(String haystack, String needle){
int result = 0;
if (haystack.equals("") || needle.length() > haystack.length()){
return -1;
}
if (needle.equals("")){
return 0;
}
int left = 0, right = needle.length();
while (right < haystack.length()){
String substring = haystack.substring(left, right);
if (substring.equals(needle)){
return left;
}
left++;
right++;
}
return result;
}
35、搜索插入位置
很简单的一题、没啥可说的。
给你一个无重复元素的升序数组、给你一个target、找出target的插入位置。如果已有target则返回索引
private static int searchInsertPosition(int[] nums, int target){
if (target < nums[0]){
return 0;
}
if (target > nums[nums.length - 1]){
return nums.length;
}
int left = 0, right = nums.length - 1;
int mid = 0;
while (left < right){
mid = (left + right) / 2;
if (nums[mid] == target){
return mid;
}
if (nums[mid] < target){
left = mid + 1;
}else {
right = mid - 1;
}
}
return mid;
}
==============================================================================
中级算法
2、两数相加【预先指针】
给你两个【非空】链表,表示两个非负整数。他们的每位数字都是按照【逆序】的方式存储的,
并且每个节点只能存储【一位】数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字0之外,这两个数都不会以0开头。
示例:l1 = [2, 4, 3] 、 l2 = [5, 6, 4]、 342 + 465 = 708 、、 return [7, 0, 8]
先看看我的解题思路:代码有点复杂
就是用简单的:每一位与每一位相加,>0则往上一位进1。我这里没有给短的List用0补全。
而LetCode大佬的解法是:把短的这个List用0补全——>构造出 l1长度 == l2,然后进行进位运算
public ListNode addTwoNumbers(ListNode l1,ListNode l2){
// 用于存储最后结果的list链表
LinkedList<Integer> list = new LinkedList<>();
//存储每次要进位的数
int z = 0;
// 如果传进来的l1 > l2 则交换一下位置
if (l1.size() > l2.size()){
return addTwoNumbers(l2,l1);
}
Iterator i1 = l1.iterator();
Iterator i2 = l2.iterator();
// 小的链表驱动大的表
while (i1.hasNext()){
// 取出l1的尾元素
int last1 = l1.removeLast();
// 取出l2的尾元素
int last2 = l2.removeLast();
// 计算两之和
int sum = last1 + last2 + z;
// 求出余数
int y = sum % 10;
// 求出进位的数 18则进位1 即 z = 1
z = sum / 10;
// 余数即可放入return的链表中了
list.add(y);
}
// 当小的链表都取完了之后,直接取大的链表剩余的部分即可。
for (int i = 0; i < l2.size(); i++) {
int last = l2.removeLast();
// 注意也要加上之前余留的进位数 z
int sum = last + z;
int y = sum % 10;
z = sum / 10;
// 把余数加到return的list中
list.add(y);
}
// 最后,如果进位!=0,则说明还没加完,把最后这个进位加到末尾即可。
if (y != 0){
list.add(z);
}
return list;
}
3、无重复字符的最长子串【滑动窗口】
给定一个字符串s,请你找出其中不含有重复字符的【最长子串】的长度
例如:输入s = “pwwkew” 输出3
// 博主的渣渣解题方法: 时间复杂度为O(m)
public int getMaxLengthOfString(String s){
String result = "";
int max = 1;
for (int i = 0; i < s.length(); i++) {
String c = String.valueOf(s.charAt(i));
System.out.println("char[" + i + "] -->" + c);
System.out.println("result --pre-->" + result);
// 判断目前筛选出的无重复字符的字符串result中,是否含有将要比对的这个字符 c
if (result.contains(c)){
System.out.println("此次result为" + result + ",发现重复字符:" + c);
max = Math.max(max,result.length());
System.out.println("目前筛选出的不重复字符串result的最大长度为:" + max);
// 重置之前筛选出的result为当前发现的这个重复字符:继续往后筛选比对
result = c;
System.out.println("重置result! 重置后的result为:" + result);
}else {
System.out.println("当前result中不包含"+ c +",将" + c + "加入result中...");
result = result + c;
System.out.println("result --post-->" + result);
}
System.out.println("------------------------------>");
}
System.out.println("筛选完毕,返回结果------->");
return max;
}
LetCode大佬的【滑动窗口】算法
窗口:内含无重复字符的最长子串。每次找到重复字符,指针滑动到重复字符处!
坚持寻找无重复字符找了好久都没重复!突然发现了一个重复的!前功尽弃!在这里重新开始
private static int getMaxLengthOfString(String s){
int n = s.length(), ans = 0;
// key为字符 value为下标+1
// map中存着目前已经扫描到的字符及其下标
Map<Character, Integer> map = new HashMap<>();
for (int end = 0, start = 0; end < n; end++) {
char alpha = s.charAt(end);
if (map.containsKey(alpha)) {
// 如果map中已经包含的这个字符:即发现了重复字符
// 则将起始指针移至重复字符的下标处
start = Math.max(map.get(alpha), start);
}
// 不包含则将本次的长度置为新的最大长度ans
ans = Math.max(ans, end - start + 1);
// 并将本次扫描过的字符放到map中, 以便下次contains比较
map.put(s.charAt(end), end + 1);
}
return ans;
}
5、最长回文子串【动态规划】
给你一个字符串s,找到s中的最长回文子串:即字符串关于中心对称(左=右)
如:s=“babad” return “bab”; s=“cbbd” return “bb”;
解法:动态规划!为减少重复计算:
每次都需要对内层字符串是否为回文串进行重复判断
将内层字符串是否为回文串缓存起来!这样就不用重复判断了
用一个boolean dp[l][r] (类似Redis,缓存着上一次的回文串) 表示字符串从i -> j是否为回文子串。
要判断i->j为回文子串,dp[l][r]=true===>即要判断它的前一位是否为回文子串dp[l-1][r-1]=true
public String longestPalindrome(String s) {
// 如果s的长度为1、那回文串就是她本身
if (s == null || s.length() < 2) {
return s;
}
int strLen = s.length();
int maxStart = 0; //最长回文串的起点
int maxEnd = 0; //最长回文串的终点
int maxLen = 1; //最长回文串的长度
// 类似redis:缓存着上一轮的最大回文子串
boolean[][] dp = new boolean[strLen][strLen];
// r指针从1开始 -> 末尾
for (int r = 1; r < strLen; r++) {
// l指针从0开始 -> r ==> 这样循环下来就能从左到右把所有可能性都遍历一次
for (int l = 0; l < r; l++) {
// 如果本次l=r,那么就要看看他们的前一位是否也为回文子串
// 1.如果r-l<=2即长度为3的时候,那就不用判断dp=true。直接为true!eg:bab
if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
// 判断成功!将本次true记录一下
dp[l][r] = true;
// 并且如果本次长度合法、则记录头指针和尾指针、用于返回最后结果
if (r - l + 1 > maxLen) {
maxLen = r - l + 1;
maxStart = l;
maxEnd = r;
}
}
}
}
return s.substring(maxStart, maxEnd + 1);
}
6、Z字形变换
将一个给定字符串s根据给定的行数numRows,从上往下、从左往右进行Z字排序
eg:输入PAYPALISHIRING,numRows=3时。排列为:
之后:将该Z字形排列从左往右逐行读取,产生新的字符串:PAHNAPLSIIGYIR
解答:
何时Z字形字符的方向开始变化!
1、每次在行数 = 0 和 numRows - 1 (即两端处方向发生变换)
2、rowNow = 0 时下一个元素所在行号:为本次行号 + 1,为numRows - 1时:回头:为本次行号 - 1
3、定义一个boolean的flag来标志下一行行号是该 + 1 还是 - 1 ?
用什么数据结构来存储每一行读出的字符?并方便最后结果的拼接?
4、定义一个String类型数组、长度为numRows、数组下标0对应第0行读出的字符、下标1对应第一行

private static String convert(String s, int numRows){
if (numRows == 1){
return s;
}
// 这里按道理是直接取(行数)len = numRows,但要考虑特殊情况。
// 如:s的长度比numRows还小时,直接输出s即可
int len = Math.min(s.length(), numRows);
// 声明一个字符串数组:下标为 0 1 2的分别存储第0 1 2行的字符
// 最后再将这个数组中的字符串依次拼接即可
String rows[] = new String[len];
for (int row = 0; row < len; row++) {
// 先通过for循环把需要的几行数组元素初始化为空串
rows[row] = "";
}
// down为false时,为向右上走(i-1)。true为向下走(i+1)
boolean down = false;
// 定义当前所在行
int rowNow = 0;
// 开始顺序遍历s字符串:
for (int i = 0; i < s.length(); i++) {
// 第0个放第0行中,第1个放第1行...遇到方向变化则回头
rows[rowNow] += s.charAt(i);
// 在当前行数为0;或为numRows - 1:即在两端口时,需要变换方向
if (rowNow == 0 || rowNow == numRows -1){
down = ! down;
}
// 下次循环的行数rowNow是根据转向标志flag来判断是+1还是-1;
rowNow += down ? 1 : -1;
}
// 至此,String数组中每个元素都存着对应下标行读取出的字符
// 因此把他们从0->len拼接起来即可
String result = "";
for (int i = 0; i < len; i++) {
System.out.println("--第" + i +

最低0.47元/天 解锁文章
636

被折叠的 条评论
为什么被折叠?



