1、反转链表
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
1、翻转链表 2、保存头部 head
1、循环:
思路:
* 1、保存上一个节点,当前节点指向上一个节点,然后再进行下一个节点变更,直到终点。
public static Node reversal(Node head){
Node current=head;
Node pre = null;
while (current!=null){
Node next = current.getNext();//临时保存 下一个节点
current.setNext(pre);//新规则 实现反转
pre = current;
current = next;//最后 current == null 终点 pre是最后一个节点
}
return pre;
}
2、递归:
递归,在反转当前节点之前先反转后续节点
2、有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
思路:
1、碰到 右括号
2、消减对应的左括号。
3、看看最后 是否存在 元素。
用栈 statck 来push 左括号,遇到右括号 就进行判断进行对应的消减。
3、用栈实现队列
正正得负, 2个栈 先进后出 得到 先进先出。
4、用队列实现栈
2个队列 先进先出,截留【最后一个元素】,得到 先进后出。
5、数据流中的第K大元素
思路:有线队列排序。
优先队列,一个倒三角形状。
java PriorityQueue 默认是 小顶堆,可以通过Comparator 函数来实现为大顶堆。
6、滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。
返回滑动窗口最大值。
思路:保留1个临时变量max 和 队列,如果右边比左边大 那么Update,否则队列顺序清除。
因为:if 右边 > 左边,左边永无出头之日。
7、有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
思路:字母数量,建议用 int[] 高效,最后 Arrays.eauals() 比较2个 int[]。
8、两数之和、三数之和 15、四数之和。
a、给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
思路:Set 集合来剪枝。
b、给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
思路:固定 第 1 个数,固定第 2 个数,多个 set 剪枝第三个数。
排序--目的:更好的剪枝。
判断是否左边最小是否能得到最小 target 值。pass
第一个数、相同成功的数。pass
第一、二个数和相同 成功的数。pass
c、给定一个包含 n 个整数的数组 nums
和一个目标值 target
,判断 nums
中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target
相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
思路:更多的剪枝。
未完待续。。。。。。
9、验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
* A
* B C
* 前序遍历:ABC 根-》左-》右
* 中序遍历:BAC 左-》根-》右 遍历出的结果是有序的array
* 后序遍历:BCA 左-》右-》根
思路:递归,最小值 < 节点 < 最大值 左节点<root<右节点
//递归 左< 中 < 右
public boolean isValidBSTRecursion(TreeNode root,long minVal,long maxVal){
if (root == null){
return true;
}
if (root.val<=minVal||root.val>=maxVal){
return false;
}
return isValidBSTRecursion(root.left, minVal, root.val) && isValidBSTRecursion(root.right, root.val, maxVal);
}
广度优先遍历打印
二叉树的层次遍历
思路:
* 难点,如何计算每层末尾。
*
* 1、BFS 复杂度 O n ,一行一行写入。
* 2、DFS 复杂度O n , 竖着插入每行,
//1ms 666
public static void bfs(TreeNode root, List<List<Integer>> mList){
if (root == null){
return;
}
Queue<TreeNode> mQueue = new LinkedList<>();
mQueue.offer(root);
// visted = set (root); 图去重
while (!mQueue.isEmpty()){
int levelsize = mQueue.size();//每层大小
List<Integer> currentLevel = new ArrayList<>();
for (int i = 0; i < levelsize; i++) {
TreeNode currentNode = mQueue.poll();
currentLevel.add(currentNode.val);
if (currentNode.left != null) {
mQueue.offer(currentNode.left);
}
if (currentNode.right != null) {
mQueue.offer(currentNode.right);
}
}
mList.add(currentLevel);
}
}
DFS 遍历, 复杂度O (n) , 竖着插入每行。
// preOrderTraverse(treeRootNode,mList,0);
/**
*
* @param root
* @param mList
* @param level 记录每行层数
*/
public static void preOrderTraverse(TreeNode root, List<List<Integer>> mList,int level){
if (root==null){
return;
}
//根
if (mList.size()>level) {
List<Integer> integers = mList.get(level);
integers.add(root.val);
}else {
ArrayList<Integer> temp = new ArrayList<>();
temp.add(root.val);
mList.add(temp);
}
if (root.left!=null){
preOrderTraverse(root.left,mList,level+1);
}
if (root.right!=null){
preOrderTraverse(root.right,mList,level+1);
}
}
10、二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
思路:结果 与 2个节点的相对位置有3种
结果在2个点的 左边、右边、中间。
结果第一次在 2 个点的 【中间】的时候,返回结果。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null){
return root;
}
if (p.val < root.val && q.val < root.val) { //p q 都在root 左边
return lowestCommonAncestor(root.left, p, q);
}
if (q.val > root.val && p.val > root.val) {//p q 都在root 右边
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
11、Pow(x, n)
x的n次幂
// TODO: 2019/3/14 leetcode 最快 8ms
/**
* if(n == 0) return 1.0;
* double d = myPow(x, n/2);
* if(n%2 == 0) return d*d;
* if(n < 0) return d*d*(1/x);
* return d*d*x;
* @param x
* @param n
* @return
*/
public static double myPow5(double x, int n) {
// System.out.println("myPow times x=="+x+" n=="+n);
if(n == 0) return 1.0;
double d = myPow5(x, n/2);
if(n%2 == 0) {
// System.out.println("n%2==0 x=="+x+" n=="+n+" d=="+d+" d*d="+(d*d));
return d*d;
}
if(n < 0) {
//TODO 如果是负数,那么递归结束前 0 -->0 -1 -2 ,肯定必须执行-1 ,即(1/x) 。那么,n%2 == 0 时只会执行 1/x 的幂,
//TODO 这个函数 代替了 d*d*x 当 n<0 时
// TODO 结果:当 n<0 且 n%2 != 0 时, 都会在此 (1/x)
// System.out.println("n<0 x=="+x+" n=="+n+" d=="+d+" (1/x)="+(1/x)+" d*d*(1/x)=="+d*d*(1/x));
return d*d*(1/x);
}
// System.out.println("d*d*x x=="+x+" n=="+n+" d=="+d+" d*d*x="+(d*d*x));
return d*d*x;
}
// TODO 正确答案 自写第一次递归算法 int 最小值取反
private static double myPow(double x, int n) {
//结束条件
if (n == 0){
return 1.0;
}
//递归条件
if (n<0){// -2^31——2^31-1,即-2147483648——2147483647 1、对于正数来说,它的补码就是它本身
//todo 如果 n 为int 最小值 -n 则超过int型最大值。 java.lang.StackOverflowError
if (n!=Integer.MIN_VALUE){
return 1 / myPow(x,-n);
}else {
return 1/ (x * myPow(x,Integer.MAX_VALUE));
}
}
//奇数 -1 相城
double v = myPow(x, n / 2);
if(n%2 == 0){
return v * v; //处理递归后返回结果
}else {
return v * v * x; //处理递归后返回结果
}
}
12、求众数
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋
的元素。
* 1、思路一 map 技术暴力法 时间 on 空间 on
* 2、思路二 排序nlogn,记录下标计数 时间 on 空间 o1
* 3、 计数不排序 2层循环 O n^2
* 4、分治算法 NlogN 2遍找 左 右 那边大返回哪边,最后比较是否大于n/2
// 2、思路二 排序nlogn,记录下标计数 时间 on 空间 o1
public static int majorityElement(int[] nums) {
Arrays.sort(nums);
int countMax = 0;
int numMax = 0;
int reultCount = nums.length / 2;
int countNow=0;
int numNow=nums[0];
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
if (num==numNow){
countNow++;
}else {
if (countNow>reultCount){
return numNow;
}
//下一个数
if (countNow>countMax){
countMax = countNow;
numMax = numNow;
}
countNow=0;
numNow=num;
countNow++;
}
}
//最后一个数
if (countNow>countMax){
countMax = countNow;
numMax = numNow;
}
return numMax;
}
/**
* 摩尔投票法的基本思想,
* 在每一轮投票过程中,从数组中找出一对不同的元素,将其从数组中删除。
* 这样不断的删除直到无法再进行投票,如果数组为空,则没有任何元素出现的次数超过该数组长度的一半。
* 如果只存在一种元素,那么这个元素则可能为目标元素。
* @param nums
* @return
*/
public static int majorityElementLeetCode_5ms(int[] nums) {
//注意:摩尔投票法,重要前提!!!众数一定存在,即一个数出现次数多于一半
int res = 0, cnt = 0;
for (int num : nums) {
if (cnt == 0) {res = num; ++cnt;}
else if (num == res) ++cnt;
else --cnt;
}
return res;
}
13、买卖股票的最佳时机 II
贪心算法,局部最优
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:
* 1、 1次买卖的话 找最大,最小值
* 2、多次买卖的话找到一段时间的最大值然后卖出,在找到一段时间最大值,再次卖出。
//贪心算法 无手续费, 前一天比今天低,那么就 买卖。
public static int maxProfit(int[] prices){
if (prices==null || prices.length<1){
return 0;
}
int fit=0;
int buy=0;
for (int i = 0; i < prices.length; i++) {
int price = prices[i];
if (i>0){
buy = prices[i - 1];
if (buy<price)
fit+=(price-buy);
}
}
return fit;
}
14、 括号生成 ,有效括号数量。
例如,给出 n = 3,生成结果为:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
public static void main(String[] args) {
List<String> strings = generateParenthesis(2);
PrintUtil.print(strings);
}
public static List<String> generateParenthesis(int n) {
List<String> ans = new ArrayList<>();
backtrack(ans,"",0,0,n);
return ans;
}
//回溯法
/**
* 序列仍然保持有效时才添加 '(' or ')'
* @param ans
* @param s
* @param open
* @param close
* @param n
*/
private static void backtrack(List<String> ans, String s, int open, int close, int n) {
if (s.length()==n*2){
ans.add(s);
} else {
if (open<n){
backtrack(ans,s+"(",open+1,close,n);
}
if (close<open){
backtrack(ans,s+")",open,close+1,n);
}
}
}
15、NQueens
思路:
* 行、列、撇、捺、
* 行 y n+1
* 列 x缓存 set
* 撇 x-y=c
* 捺 x+y=q
+回溯
public static List<String[]> solve(int n){
ArrayList<String[]> res = new ArrayList<>();
boolean [] col = new boolean[n];
boolean [] pie = new boolean[2*n];
boolean [] na = new boolean[2*n];
String[] map = new String[n];
System.out.println(map.length+"----");
getbackTwo(res,map,n,0,col,pie,na);
return res;
}
//用 数组替换集合
private static void getbackTwo(ArrayList<String[]> res, String[] map, int n, int row, boolean[] col, boolean[] pie, boolean[] na) {
if (row == n){
// res.add(map); TODO
res.add(map.clone());//因为回溯 替换之间的解会 操作同一地址的数组 进而改变结果。
return;
}
for (int x = 0; x < n; x++) {
if (!col[x]&&!pie[x-row+n] &&!na[n*2 - x - row - 1]){
char[] r = new char[n];
Arrays.fill(r,'.');
r [ x ] = 'Q';
map[row] = new String(r);
col[x] = true;
pie[x-row+n] = true;
na[n*2 - x - row -1] = true;
getbackTwo(res,map, n, row+1,col,pie,na);
//回溯
col[x] = false;
pie[x-row+n] = false;
na[n*2 - x - row -1] = false;
}
}
}
16、解数独
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
空白格用 '.'
表示。
一个数独。
答案被标成红色。
方法1 暴力法 :
1、检索出每个待填入的数字。
2、填入依次填入合法的数字。
3、递归,进行下一个空格填写。
4、if 返回值正确,那么继续。else 回溯 并 进行下一个数字尝试。
5、默认有解
//求解 数独
public static void solveSudoku(char[][] board) {
if (board == null || board.length == 0)return;
solve(board);
// inputNumer(board);
// if (clone!=null){
// for (int i = 0; i < clone.length; i++) {
// System.arraycopy(clone[i], 0, board[i], 0, clone[i].length);
// }
// }
}
private static boolean solve(char[][] board) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] =='.'){
// TODO: 2019/2/25 待验证 有问题
// for (char k = '1'; k <='9' ; k++) {
// if (isValid(board,i,j,k)) {
// board[i][j] = k;
// if (solve(board)){
// return true;
// }else {
// board[i][j]='.';//other go back
// }
// }
// }
// return false;
HashSet<Character> isValid = getValidChars(board,i,j);
for (Character anIsValid : isValid) {
board[i][j] = anIsValid;
if (solve(board)) {
return true;
} else {
board[i][j] = '.';//other go back TODO 为什么? 如果这是最后一个选项但是错了,那么如何回溯,只有set . 因为所有操作只是一个数组对象。
}
}
//如果 所有可能的选择数字都无解 那么返回false
return false;
}
}
}
return true; //默认 有解
}
private static HashSet<Character> getValidChars(char[][] board, int i, int j ) {
HashSet<Character> characters = new HashSet<>();
for (char k = '1'; k <= '9'; k++) {
characters.add(k);
}
for (int k = 0; k < board.length; k++) {
char r = board[i][k];
char c = board[k][j];
characters.remove(r);
characters.remove(c);
}
for (int k = i / 3 * 3; k < ( i / 3 * 3 + 3) ; k++) {
for (int m = j / 3 * 3; m < ( j / 3 * 3 + 3) ; m++) {
char b = board[k][m];
characters.remove(b);
}
}
return characters;
}
方法二:更多的剪枝,eg :
1、check 每一个空格的可选数字的数量,排序。 先填写最小可选量的格子。
2、数据结构 全部应用数组 。
3、int 与 char 转换。
方法三:更优的数据结构 Dancing Links 求解数独。
17.前缀树 208
只包含 3中操作
1、insert
2、search
3、startsWith
思路:
1、多个链表 trieNode 头尾相连
2、在 word end 时标记 true
动态规划
* 1、递推!!!=(递归 + 记忆化)
* 2、状态的定义 : opt[n],dp[n],fib[n] * opt 数组 * dp 方程
* 3、状态转移方程: opt[n] = best_of (opt[n-1],opt[n-2],...)
* 4、最优子结构 (目的:可以有小推大,递推)
1、动态规划 菲波那切数列
原始递归:时间复杂度 O 2^n
//从上往下 推
public static int fib(int n){
return n <=1 ? n :fib(n-1) + fib(n-2);
}
多个fit 重复计算了。
可以增加缓存后 时间复杂度 O(n)
//精髓,从下往上逆推 数组存储计算的值
public static int fibDynamic (int n){
int[] F = new int [n + 1];
F[0] = 0;
if (n==0)
return 0;
F[1] = 1;
for (int i = 2; i <= n; i++) {
F[i] = F[i-1] +F[i-2];
}
return F[n];
}
2、动态规划 Count Paths
找到 起点到终点的路径数量。
思路:起始点的解 = 下面空格解 + 右边空格的解;(存在最优子结构 可以 递归)
递归公式:
public static void main(String[] args) {
//8行 8列
int [][] field = new int[8][8];//第一个8表示某行,即y坐标
// start[7][0];
// end[0][7];
//标注石头位置
ston = -1;
field[1][1] = ston;
field[1][5] = ston;
field[2][3] = ston;
field[2][4] = ston;
field[2][6] = ston;
field[3][2] = ston;
field[4][0] = ston;
field[4][2] = ston;
field[4][5] = ston;
field[5][4] = ston;
field[6][2] = ston;
field[6][6] = ston;
int [][] fieldCount = new int[8][8];//第一个8表示某行,即y坐标
//初始值
fieldCount[0][7]=0;
fieldCount[0][6]=1;
fieldCount[1][7]=1;
//从终点开始到出发点结束
for (int i = 0; i < 8; i++) {//行
for (int j = 7; j >=0 ; j--) { //列
// start
if( field[i][j] == ston ){
fieldCount[i][j] = 0;
}else {
if (i==0 && j==7){
}else if (i==0 && j==6){
} else if (i==1 && j==7){
}else {
fieldCount[i][j] = ((i-1)< 0 ? 0 :fieldCount[i-1][j]) + ((j+1) > 7 ? 0 : fieldCount[i][j+1]);
}
}
}
}
PrintUtil.printArraysInteger(fieldCount);
System.out.println(fieldCount[7][0]);
}
打印结果:
动态规划, 优于 递归 、贪心。
参考:极客时间 【谭超】算法讲解