Merge branch 'master' of github.com:nonstriater/Learn-Algorithms
This commit is contained in:
@@ -4,11 +4,21 @@
|
||||
|
||||
* 回文判断
|
||||
* 字符(串)统计
|
||||
* [子串查找](1.1%20字符串-查找.md)
|
||||
* [字符串修改](1.3%20字符串-修改.md) 如单词反转/移动等
|
||||
* [字符删除](1.2%20字符串-删除.md)
|
||||
* 字符串的压缩
|
||||
* 字符串的排列和组合
|
||||
* [子串查找](1.1%20字符串-查找.md)
|
||||
* [字符删除](1.2%20字符串-删除.md)
|
||||
* [字符串修改](1.3%20字符串-修改.md) 如单词反转/移动等
|
||||
|
||||
|
||||
## Java 字符串处理 常用 API
|
||||
|
||||
```
|
||||
String
|
||||
charAt(index)
|
||||
char[]
|
||||
Map<Character,Integer>
|
||||
```
|
||||
|
||||
|
||||
## 判断字符串是否是回文
|
||||
|
||||
@@ -27,11 +27,14 @@ char char_first_appear_once(const unsigned char *source){
|
||||
int hash[256]={0};
|
||||
char *tmp = source;
|
||||
if (tmp == NULL) return '\0';
|
||||
|
||||
//第一次建立hash表 key为字符,value为出现次数
|
||||
while(*tmp != '\0'){
|
||||
hash[*tmp]++;
|
||||
tmp++;
|
||||
}
|
||||
|
||||
//第二次扫描找到第一个value为1的key
|
||||
tmp = source;
|
||||
while(*tmp != '\0'){
|
||||
if (hash[*tmp] == 1) return *tmp;
|
||||
@@ -45,19 +48,6 @@ char char_first_appear_once(const unsigned char *source){
|
||||
题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容
|
||||
|
||||
|
||||
## 请编写能直接实现strstr()函数功能的代码。
|
||||
|
||||
> strstr(str1,str2) 判断str2是否是str1的子串。
|
||||
|
||||
```
|
||||
/*
|
||||
@ret 有就返回第一次出现子串的地址,否则返回NULL
|
||||
*/
|
||||
char *strstr(const char *source, const char *target){
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 字符串中找出连续最长的数字子串
|
||||
|
||||
@@ -164,9 +154,68 @@ int lengthOfLongestSubstring(string s) {
|
||||
|
||||
|
||||
|
||||
## 求最大连续递增数字子串
|
||||
## 对称子字符串的最大长度 (最长回文子串)
|
||||
|
||||
题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。
|
||||
|
||||
分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。
|
||||
|
||||
```
|
||||
int max_symmetrical_char_length(const char *scr);
|
||||
```
|
||||
|
||||
* 思路一:蛮力法,3重循环(类似 求子数组的最大和 fmax(i,j)问题), fmax(i,j)区间i,j是最长的对称字符
|
||||
* 思路二:遍历所有子串,然后判读是否对称 O(n^2)
|
||||
* 思路三:有个O(n)复杂度的算法 http://www.cnblogs.com/McQueen1987/p/3559497.html 分析过程如下:
|
||||
|
||||
|
||||
```
|
||||
int max_symmetrical_char_length(const char *scr){
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```Java
|
||||
public String longestPalindrome(String s) {
|
||||
String res = "";
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
// 以 s[i] 为中心的最长回文子串
|
||||
String s1 = palindrome(s, i, i);
|
||||
|
||||
// 以 s[i] 和 s[i+1] 为中心的最长回文子串
|
||||
String s2 = palindrome(s, i, i + 1);
|
||||
|
||||
// res = longest(res, s1, s2)
|
||||
res = res.length() > s1.length() ? res : s1;
|
||||
res = res.length() > s2.length() ? res : s2;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//实现一个函数来寻找最长回文串
|
||||
String palindrome(String s, int l, int r) {
|
||||
// 防止索引越界
|
||||
while (l >= 0 && r < s.length()
|
||||
&& s.charAt(l) == s.charAt(r)) {
|
||||
// 向两边展开
|
||||
l--; r++;
|
||||
}
|
||||
// 返回以 s[l] 和 s[r] 为中心的最长回文串
|
||||
return s.substring(l + 1, r);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 最小覆盖子串
|
||||
|
||||
(难度 hard) `滑动窗口`
|
||||
|
||||
给定 字符串 s, t ; 在字符串 s 里找出 包含 t 所有字母的最小子串
|
||||
eg: s = "ADOBECODEBANC" t = "ABC" 输出: "BANC"
|
||||
|
||||
|
||||
|
||||
如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。
|
||||
|
||||
|
||||
|
||||
@@ -193,24 +242,22 @@ int longest_common_subsequence(const char *s1,const char *s2, char *common){
|
||||
```
|
||||
|
||||
|
||||
## 对称子字符串的最大长度
|
||||
|
||||
题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。
|
||||
## 求最大连续递增数字子串
|
||||
|
||||
分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。
|
||||
如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。
|
||||
|
||||
|
||||
## 请编写能直接实现strstr()函数功能的代码。
|
||||
|
||||
> strstr(str1,str2) 判断str2是否是str1的子串。
|
||||
|
||||
```
|
||||
int max_symmetrical_char_length(const char *scr);
|
||||
```
|
||||
|
||||
* 思路一:蛮力法,3重循环(类似 求子数组的最大和 fmax(i,j)问题), fmax(i,j)区间i,j是最长的对称字符
|
||||
* 思路二:遍历所有子串,然后判读是否对称 O(n^2)
|
||||
* 思路三:有个O(n)复杂度的算法 http://www.cnblogs.com/McQueen1987/p/3559497.html 分析过程如下:
|
||||
|
||||
|
||||
```
|
||||
int max_symmetrical_char_length(const char *scr){
|
||||
|
||||
/*
|
||||
@ret 有就返回第一次出现子串的地址,否则返回NULL
|
||||
*/
|
||||
char *strstr(const char *source, const char *target){
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -79,22 +79,6 @@ char *revert_by_word(char *source){
|
||||
用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。
|
||||
|
||||
|
||||
## 左旋转字符串
|
||||
|
||||
>字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。
|
||||
|
||||
如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。
|
||||
|
||||
```
|
||||
char *left_rotate(char *str,int offset){
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。
|
||||
也就是跟上面的问题是同样的问题。
|
||||
|
||||
|
||||
|
||||
## 替换空格
|
||||
|
||||
@@ -138,6 +122,23 @@ char *replace_blank(char *source){
|
||||
```
|
||||
|
||||
|
||||
## 左旋转字符串
|
||||
|
||||
>字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。
|
||||
|
||||
如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。
|
||||
|
||||
```
|
||||
char *left_rotate(char *str,int offset){
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。
|
||||
也就是跟上面的问题是同样的问题。
|
||||
|
||||
|
||||
|
||||
|
||||
## 字符串原地压缩
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 小写字母排在大写字母的前面
|
||||
|
||||
有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型:
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
* 链表中是否有环
|
||||
|
||||
|
||||
链表结点定义如下:
|
||||
链表结点定义如下:
|
||||
```
|
||||
struct ListNode
|
||||
{
|
||||
int m_nKey;
|
||||
ListNode* m_pNext;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -110,6 +112,15 @@ struct ComplexNode
|
||||
请完成函数`ComplexNode* Clone(ComplexNode* pHead)`,以复制一个复杂链表。
|
||||
|
||||
|
||||
这个题目难点在于:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,19 @@
|
||||
|
||||
不需要考虑溢出。
|
||||
|
||||
|
||||
用例:
|
||||
|
||||
```
|
||||
2^3 = 8
|
||||
0^3 = 0
|
||||
2^0 = 1
|
||||
2^-3 = 1/(2^3) = 0.125
|
||||
0^-3
|
||||
```
|
||||
|
||||
|
||||
|
||||
分析:这是一道看起来很简单的问题。可能有不少的人在看到题目后30秒写出如下的代码:
|
||||
```
|
||||
double Power(double base, int exponent)
|
||||
@@ -35,15 +48,6 @@ double Power(double base, int exponent)
|
||||
2^1 = 2
|
||||
```
|
||||
|
||||
用例:
|
||||
|
||||
```
|
||||
2 3 = 8
|
||||
0 3 = 0
|
||||
2 0 = 1
|
||||
2 -3 = 1/(2^3) = 0.125
|
||||
0 -3
|
||||
```
|
||||
|
||||
使用递归实现的代码示例:
|
||||
|
||||
@@ -62,6 +66,25 @@ double Power(double base, unsigned int exponent)
|
||||
```
|
||||
|
||||
|
||||
## Sqrt(x)
|
||||
|
||||
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
|
||||
|
||||
|
||||
示例 1:
|
||||
```
|
||||
输入:x = 4
|
||||
输出:2
|
||||
```
|
||||
示例 2:
|
||||
```
|
||||
输入:x = 8
|
||||
输出:2
|
||||
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 求根号2的值
|
||||
|
||||
并且按照我的需要列出指定小数位,比如根号2是1.141 我要列出1位小数就是1.1 2位就是1.14, 1000位就是1.141...... 等。。
|
||||
|
||||
@@ -80,8 +80,6 @@ int fib(int n) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 递减数列左移后的数组中找数
|
||||
|
||||
一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
|
||||
|
||||
@@ -25,8 +25,6 @@ void sort(int * a, int length)
|
||||
|
||||
`动态规划`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 扑克牌的顺子
|
||||
|
||||
@@ -81,7 +81,6 @@ int findaddends(int *data,int length,int sum,int *a,int *b);
|
||||
|
||||
|
||||
|
||||
|
||||
## 求子数组的最大和(最大字段和)
|
||||
|
||||
输入一个整形数组,数组里有正数也有负数。
|
||||
@@ -91,7 +90,7 @@ int findaddends(int *data,int length,int sum,int *a,int *b);
|
||||
例如输入的数组为`1, -2, 3, 10, -4, 7, 2, -5` ,和最大的子数组为`3, 10, -4, 7, 2` ,因此输出为该子数组的和18。
|
||||
|
||||
1. 蛮力法 fmax(i,j) 找出最大的值,3重循环 ,复杂度 0(n^3)
|
||||
2. maxendindhere保存当前累加的和,如果<0,就把maxendinghere清零 , max保存最终的最大和
|
||||
2. `动态规划` maxendindhere保存当前累加的和,如果<0, 就把maxendinghere清零 , max保存最终的最大和; 如果都是负数
|
||||
```
|
||||
int maxSumOfVector(int *data,int length){
|
||||
|
||||
|
||||
@@ -87,13 +87,64 @@ topMaxK(int *a,int length, int k);
|
||||
|
||||
## 找第k大的数
|
||||
|
||||
比如 1,2,3,4,5,6,7和8这8个数字,低3大的数字是 6
|
||||
比如 `1,2,3,4,5,6,7,8 `这 8个数字,第3大的数字是 6
|
||||
|
||||
```
|
||||
int topK(int * a, int length, int k)
|
||||
```
|
||||
|
||||
|
||||
```Java
|
||||
//冒泡实现
|
||||
public int findK(int[] nums, int k){
|
||||
//base case
|
||||
if (nums== null || nums.length < k) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(int i=1; i<=k; i++){
|
||||
for(int j=0; j<nums.length-i; j++) {
|
||||
int next = j+1;
|
||||
if(nums[j]> nums[next]){
|
||||
int tmp = nums[j];
|
||||
nums[j] = nums[next];
|
||||
nums[next] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nums[nums.length-k];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
```Java
|
||||
/**
|
||||
* 小根堆实现
|
||||
*/
|
||||
public static int findMaxK(int[] nums, int k) {
|
||||
PriorityQueue<Integer> pq = new PriorityQueue<>(k, (a, b) -> (a-b) );
|
||||
|
||||
for (int i = 0; i <nums.length ; i++) {
|
||||
//取出前k个元素放入 PQ 中
|
||||
if (i < k) {
|
||||
pq.add(nums[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer head = pq.peek();
|
||||
if (head < nums[i]) {//维护 priorityQueue 中元素只有k个
|
||||
pq.poll();
|
||||
pq.add(nums[i]);
|
||||
}
|
||||
}
|
||||
return pq.poll();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 最长公共子序列 (动态规划的经典题目)
|
||||
|
||||
@@ -141,4 +192,35 @@ int one_appear_count(int n);
|
||||
|
||||
|
||||
|
||||
## 搜索旋转排序数组
|
||||
|
||||
|
||||
整数数组 nums 按升序排列,数组中的值 互不相同 。
|
||||
|
||||
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
|
||||
|
||||
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
|
||||
|
||||
|
||||
示例 1:
|
||||
|
||||
```
|
||||
输入:nums = [4,5,6,7,0,1,2], target = 0
|
||||
输出:4
|
||||
```
|
||||
|
||||
示例 2:
|
||||
```
|
||||
输入:nums = [4,5,6,7,0,1,2], target = 3
|
||||
输出:-1
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,21 @@ struct SBinaryTreeNode // a node of the binary tree
|
||||
};
|
||||
```
|
||||
|
||||
二叉树递归框架
|
||||
|
||||
```Java
|
||||
|
||||
/* 二叉树遍历框架 */
|
||||
void traverse(TreeNode root) {
|
||||
// 前序遍历
|
||||
traverse(root.left)
|
||||
// 中序遍历
|
||||
traverse(root.right)
|
||||
// 后序遍历
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二叉树翻转
|
||||
@@ -92,8 +107,7 @@ TreeNode invertTree(TreeNode root) {
|
||||
|
||||
|
||||
|
||||
|
||||
## 二叉树两个结点的最低共同父结点
|
||||
## 二叉树两个结点的最低共同父结点 (最近公共祖先)
|
||||
|
||||
设计一个算法,找出二叉树上任意两个结点的最近共同父结点。复杂度如果是O(n2)则不得分
|
||||
|
||||
@@ -103,6 +117,29 @@ TreeNode invertTree(TreeNode root) {
|
||||
分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。
|
||||
|
||||
|
||||
```
|
||||
TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
// base case
|
||||
if (root == null) return null;
|
||||
if (root == p || root == q) return root;
|
||||
|
||||
TreeNode left = lowestCommonAncestor(root.left, p, q);
|
||||
TreeNode right = lowestCommonAncestor(root.right, p, q);
|
||||
// 情况 1 :p, q 在 root 为根的树中
|
||||
if (left != null && right != null) {
|
||||
return root;
|
||||
}
|
||||
// 情况 2 :p, q 不在 在 root 为根的树中
|
||||
if (left == null && right == null) {
|
||||
return null;
|
||||
}
|
||||
// 情况 3 : p, q 只有1个在root 为根的树中
|
||||
return left == null ? right : left;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 求一个二叉树中任意两个节点间的最大距离
|
||||
|
||||
|
||||
@@ -49,13 +49,36 @@
|
||||
分析:这道题本质上还是考查二元树的遍历。对于一颗完全二叉树,要求给所有节点加上一个pNext指针,指向同一层的相邻节点;如果当前节点已经是该层的最后一个节点,则将pNext指针指向NULL;给出程序实现,并分析时间复杂度和空间复杂度。
|
||||
|
||||
|
||||
## 二元树的最小深度
|
||||
|
||||
|
||||
例如:输入二元树:
|
||||
```
|
||||
|
||||
10
|
||||
/ \
|
||||
6 14
|
||||
/ \
|
||||
12 16
|
||||
```
|
||||
|
||||
输出该树的最小深度2。
|
||||
|
||||
|
||||
|
||||
## 完全二叉树的节点个数
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 判断整数序列是不是二元查找树的后序遍历结果
|
||||
|
||||
题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。
|
||||
如果是返回true,否则返回false。
|
||||
例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
|
||||
|
||||
例如输入`5、7、6、9、11、10、8`,由于这一整数序列是如下树的后序遍历结果:
|
||||
```
|
||||
8
|
||||
/ \
|
||||
@@ -66,3 +89,9 @@
|
||||
因此返回true。如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
# 二叉树-建树
|
||||
|
||||
|
||||
|
||||
|
||||
## 把一个有序整数数组放到二叉树中?
|
||||
|
||||
分析:本题考察二叉搜索树的建树方法,简单的递归结构。
|
||||
|
||||
关于树的算法设计一定要联想到递归,因为树本身就是递归的定义。
|
||||
而,学会把递归改称非递归也是一种必要的技术。
|
||||
毕竟,递归会造成栈溢出,关于系统底层的程序中不到非不得以最好不要用。
|
||||
但是对某些数学问题,就一定要学会用递归去解决。
|
||||
关于树的算法设计一定要联想到递归,因为树本身就是递归的定义。而,学会把递归改称非递归也是一种必要的技术。
|
||||
毕竟,递归会造成栈溢出,关于系统底层的程序中不到非不得以最好不要用。但是对某些数学问题,就一定要学会用递归去解决。
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# 二叉搜索树
|
||||
|
||||
关于二叉查找树特性[参考这里](../4%20Tree/2-二叉查找树/二叉查找树.md)
|
||||
|
||||
## 验证二叉搜索树
|
||||
|
||||
|
||||
## 二叉搜索树中的节点搜索
|
||||
|
||||
|
||||
## 二叉搜索树中的插入操作
|
||||
|
||||
|
||||
## 删除二叉搜索树中的节点
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,275 @@
|
||||
|
||||
这部分内容是算法问题合集,题目大多来自网络和书籍。我做了下简单的整理,很多题做了一些思路标记。
|
||||
|
||||
## 小结
|
||||
|
||||
面试中如何更好的写算法题
|
||||
|
||||
* ide 工具 & 文本框 ;文本框环境写算法是通常没有智能提示,有些基础数据结构可能要自己定义
|
||||
* 语言基础语法:各常用集合的api,要能够默写
|
||||
* 题目:熟悉各种题目,避免在题目理解上花太多时间跟面试官拉齐
|
||||
* 编程思路和框架:拿到题目以后能快速套思路和代码框架
|
||||
* 编程细节:多练习,总结
|
||||
|
||||
|
||||
## 常用解题套路工具
|
||||
|
||||
* 数组,字符串问题:二分查找、 快慢指针、 左右指针、 滑动窗口、 前缀和数组、 差分数组。
|
||||
* 二叉树问题:递归
|
||||
* [动态规划问题](../8%20Algorithms%20Analysis/动态规划.md)
|
||||
* [常见算法题目Java实现](https://github.com/nonstriater/deep-in-java/tree/master/src/main/java/com/nonstriater/deepinjava/algo)
|
||||
|
||||
|
||||
## 刷题框架套路
|
||||
|
||||
|
||||
### 遍历
|
||||
|
||||
```
|
||||
# 数组遍历框架
|
||||
void traverse(int[] arr) {
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
// 迭代访问 arr[i]
|
||||
}
|
||||
}
|
||||
|
||||
# 链表遍历框架
|
||||
void traverse(ListNode head) {
|
||||
|
||||
ListNode p = head;
|
||||
while(p.next != null)
|
||||
...
|
||||
p = p.next
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 递归
|
||||
|
||||
```Java
|
||||
# 链表递归
|
||||
void traverse(ListNode head) {
|
||||
// 递归访问 head.val
|
||||
traverse(head.next);
|
||||
}
|
||||
|
||||
|
||||
# 二叉树递归
|
||||
void traverse(TreeNode root) {
|
||||
traverse(root.left);
|
||||
traverse(root.right);
|
||||
}
|
||||
|
||||
# 多叉树递归
|
||||
void traverse(TreeNode root) {
|
||||
for (TreeNode child : root.children)
|
||||
traverse(child);
|
||||
}
|
||||
|
||||
# 图的递归:,用个布尔数组 visited 做标记就行了
|
||||
|
||||
```
|
||||
|
||||
比如,二叉树的最近公共祖先
|
||||
|
||||
```Java
|
||||
TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
// base case
|
||||
if (root == null) return null;
|
||||
if (root == p || root == q) return root;
|
||||
|
||||
TreeNode left = lowestCommonAncestor(root.left, p, q);
|
||||
TreeNode right = lowestCommonAncestor(root.right, p, q);
|
||||
// 情况 1 :p, q 在 root 为根的树中
|
||||
if (left != null && right != null) {
|
||||
return root;
|
||||
}
|
||||
// 情况 2 :p, q 不在 在 root 为根的树中
|
||||
if (left == null && right == null) {
|
||||
return null;
|
||||
}
|
||||
// 情况 3 : p, q 只有1个在root 为根的树中
|
||||
return left == null ? right : left;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 二分查找
|
||||
|
||||
[查找算法](../7%20Search/README.md)
|
||||
|
||||
```Java
|
||||
// 1, 2, 2, 2, 2, 3
|
||||
public int bsearch(int[] nums, int target){
|
||||
|
||||
if(nums == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int low = 0;
|
||||
int high = nums.length;// 注意
|
||||
|
||||
while(low < high){
|
||||
middle = (high + low ) / 2;
|
||||
if(nums[middle] < target){
|
||||
low = middle + 1;
|
||||
} else if (nums[middle] == target) {
|
||||
high = middle;//继续向左早
|
||||
} else {
|
||||
high = middle; // 注意 , 这里没有 -1
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 快慢指针
|
||||
|
||||
如下判断链表是否有环
|
||||
|
||||
```
|
||||
public static boolean hasCycle(LinkNode head) {
|
||||
if(head == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LinkNode p = head, q = head;
|
||||
while(p.next != null && p.next.next != null && q.next != null) {
|
||||
p = p.next.next;
|
||||
q = q.next;
|
||||
if(p == q) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 左右指针
|
||||
|
||||
|
||||
快排中 挖坑(pivot) 排序
|
||||
```Java
|
||||
// pivot 选择 尾部节点, 代码写起来更加简单; 移动元素更方便
|
||||
// 左右指针技巧
|
||||
static int partition(int[] nums, int left, int right){
|
||||
|
||||
int pivot = nums[right];//选尾部节点作为 pivot
|
||||
int end = right;
|
||||
|
||||
right--;
|
||||
while (left < right) {
|
||||
|
||||
if (nums[left] <= pivot) {
|
||||
left ++ ; //左边指针 窗口变小
|
||||
continue;
|
||||
}
|
||||
|
||||
//元素比 pivot 大,右边指针 窗口变小
|
||||
//swap left & right
|
||||
swap(nums, left, right);
|
||||
right--;
|
||||
|
||||
}
|
||||
|
||||
// 跟 pivot 元素置换
|
||||
int i = 0;
|
||||
if (nums[left] <= pivot) {
|
||||
//swap left+1 & pivot
|
||||
i = left +1;
|
||||
} else {//swap left & pivot
|
||||
i = left ;
|
||||
}
|
||||
|
||||
swap(nums, i, end);
|
||||
|
||||
return i;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 滑动窗口
|
||||
|
||||
双指针中有一类比较难的技巧就是`滑动窗口`
|
||||
|
||||
滑动窗口: 无重复字符的最长子串
|
||||
|
||||
```Java
|
||||
//如 ”abcabbcbb“ 输出 3
|
||||
public static int longestSubString(char[] s){
|
||||
int left = 0, right = 0;
|
||||
int res = 0; // 记录最长结果
|
||||
|
||||
Map<Character, Integer> window = new HashMap<Character, Integer>();
|
||||
|
||||
while (right < s.length) {
|
||||
Character c = s[right];
|
||||
|
||||
//窗口变大
|
||||
right++;
|
||||
window.put(c, window.getOrDefault(c, 0) + 1); //java写的麻烦,不一定记得这个api
|
||||
|
||||
if (window.get(c) > 1) {
|
||||
//判断左侧窗口是否要收缩
|
||||
char d = s[left];
|
||||
|
||||
left ++;
|
||||
window.put(d, window.get(d)-1);
|
||||
}
|
||||
|
||||
// 在这里更新答案
|
||||
res = res > (right-left) ? res: (right-left);
|
||||
|
||||
}
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 排序算法
|
||||
|
||||
[排序算法](../6%20Sort/README.md)
|
||||
|
||||
```Java
|
||||
|
||||
#快排 quickSort
|
||||
void quicksort(int[] nums, int left, int right){
|
||||
if (left<right){//加上这个,不然有死循环,造成堆栈溢出,这也是递归结束条件
|
||||
|
||||
//前序遍历位置
|
||||
//使得局部有序,i作为分隔, i 前面的元素 小于 i 后面的元素
|
||||
int i = partion(a,left,right);
|
||||
|
||||
quicksort(a,left,i-1);
|
||||
quicksort(a,i+1,right);
|
||||
}
|
||||
}
|
||||
|
||||
#归并排序 mergeSort
|
||||
void mergeSort(int[] nums, int left, int right){
|
||||
int mid = (low + high) / 2;
|
||||
sort(nums, low, mid);
|
||||
sort(nums, mid + 1, high);
|
||||
|
||||
/****** 后序遍历位置 ******/
|
||||
// 合并两个排好序的子数组
|
||||
merge(nums, low, mid, high);
|
||||
/************************/
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
### 动态规划 DP
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [字符串](1%20字符串.md)
|
||||
|
||||
@@ -14,7 +283,6 @@
|
||||
* 字符串比较
|
||||
* 子串
|
||||
|
||||
|
||||
|
||||
## [链表](2%20链表.md)
|
||||
|
||||
@@ -60,12 +328,13 @@
|
||||
|
||||
## [二叉树](7%20二叉树.md)
|
||||
|
||||
常见问题
|
||||
|
||||
* 遍历
|
||||
* 翻转
|
||||
* 子树
|
||||
|
||||
|
||||
解题思路:递归
|
||||
|
||||
## [图](8%20图.md)
|
||||
|
||||
@@ -73,24 +342,18 @@
|
||||
|
||||
|
||||
|
||||
## 智力思维训练
|
||||
## [智力思维训练](9%20智力思维训练.md)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [《剑指offer》](剑指offer/README.md)
|
||||
|
||||
《剑指offer》里面给出了50到高质量的算法问题,很有学习的必要。
|
||||
|
||||
|
||||
## [《编程之美》](编程之美/README.md)
|
||||
|
||||
|
||||
|
||||
[九度OJ](http://ac.jobdu.com/index.php)
|
||||
[leetcode 题目](https://leetcode.com/)
|
||||
|
||||
|
||||
# 参考
|
||||
|
||||
[九度OJ](http://ac.jobdu.com/index.php)
|
||||
[leetcode 题目](https://leetcode.com/)
|
||||
[labuladong 的算法小抄](https://labuladong.gitee.io/algo/1/2/)
|
||||
[《剑指offer》](剑指offer/README.md) 《剑指offer》里面给出了50到高质量的算法问题,很有学习的必要。
|
||||
[《编程之美》](编程之美/README.md)
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user