算法01-学习总结

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 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

思路:保留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/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、 括号生成 ,有效括号数量。

例如,给出 = 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. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 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]);
    }

打印结果:

动态规划, 优于 递归 、贪心。

 

参考:极客时间 【谭超】算法讲解

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值