剑指offer 2

常见算法总结

文章目录


(1)找出数组中任意重复的数字

int findRepNums(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        //初始化
        for (int i = 0; i < nums.length; i++) {
            // 将i作为key存放 value放出现次数当出现次数>0 则找到可行解
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }

        for (int j=0;j<nums.length;j++) {
            if(map.get(nums[j]) == 2 ) {
                return nums[j];
            }
        }
        return -1;
    }

(2) 二维数组中查找

n*m的数组中,从上到下从左到右都是递增的。在矩阵中搜索目标值。

  static boolean findNumInArray(int[][] array, int target) {
        // 从斜对角开始搜索,此时数据类似一颗二叉树
        if (array.length == 0 || array[0].length == 0) {
            return false;
        }
        int m = array.length;
        int n = array[0].length;
        for (int i = 0, j = n - 1; i < m &&j >= 0;) {
            if(array[i][j] ==target) {
                return true;
            }
            if(array[i][j] > target) {
                i++;
            } else {
                j--;
            }
        }
        return false;
    }

(3)替换空格

String replaceSpace(String s) {
return s.replaceAll(" ","%20");
}

 String replaceSpace(String s)  {
        char[] chars = s.toCharArray();
        StringBuilder stringBuilder = new StringBuilder();
        for (char c : chars) {
            stringBuilder.append(c== ' ' ? "20%" : c);
        }
        return stringBuilder.toString();
    }

(4) 从尾到头打印链表

static int[] reversePrintListNode(ListNode head) {
        List<Integer> list = new ArrayList<>();
        while (head != null) {
            list.add(head.getVal());
            head = head.next;
        }
        int size = list.size();
        int[] array = new int[size];
        for (Integer num : list) {
            size--;
            array[size] = num;
        }
        return array;
    }

(5)重建二叉树

已知二叉树的前序和中序遍历的结果重构二叉树
Input preorder[3,9,20,15,7]
output inorder[9,3,15,20,7]
二叉树类型的题目分为两类一类是遍历的思维模式、一类是递归(分解问题)的思维模式;

前序遍历可以知道其根节点 preorder[0] 通过这个值可以从中序遍历中判断左右子树

 private TreeNode buildTwoTree(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
        if (preStart > preEnd) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[preStart]);
        int index = 0;
        for (int i = 0; i < inorder.length; i++) {
            if (inorder[i] == root.getVal()) {
                index = i;
                break;
            }
        }
        root.left = buildTwoTree(preorder, preStart + 1,
                preStart + index - inStart, inorder, inStart, index - 1);
        root.right = buildTwoTree(preorder, preStart + index - inStart + 1,
                preEnd, inorder, index + 1, inEnd);
        return root;
    }

(6)反转二叉树

输入二叉树的根节点将整个树镜像反转

思路就是遍历每一个节点让所有节点的左右子树交换位置
private TreeNode traverse(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        traverse(root.left);
        traverse(root.right);
        return root;
    }
递归思路:先将左子树反转,再将右子树反转,最后将根节点的左右子树反转
private TreeNode invert(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode root.left = invert(root.left);
        TreeNode root.right = invert(root.right);
        root.left = right;
        root.right = left;
        return root;
    }

(7) 填充二叉树的左右指针

TreeNode connect(TreeNode root) {
if(root == null ) return null;
traverse(root.left,root.right); 
return root 
}

void traverse(TreeNode node1,TreeNode node2) {
if(node1==null || node2 == null) {
return null;
}

node1.next = node2;
// 连接相同父节点
traverse(node1.left,node1.right);
traverse(node2.left,node2.right);
traverse(node1.left,node2.right);
}

(8) 将二叉树展开称为链表

void flatten(TreeNode root) {
if(root == null)
flatten(root.left);
flatten(root.right);
TreeNode left = root.left;
TreeNode right = root.right;
// 将左子树接到右子树上
root.left = null;
root.right = left;
// 将右子树的节点加入左子树的末尾
TreeNode p = root;
while(p.right !=null ) {
p = p.right;
}
p.right = right;
}

(9)用两个栈实现队列

public class Queue {
    private Stack<Integer> stack1 = new Stack<>();
    private Stack<Integer> stack2 = new Stack<>();

    public Queue() {
    }

    public void appendTail(int val) {
        stack1.push(val);
    }

    public int deleteHead() {
        while (!stack1.empty()) {
            stack2.push(stack1.pop());
        }
        return stack2.isEmpty() ? -1 : stack2.pop();
    }
}

(10) 斐波那契数列

   /***
     * 递归解法
     * @param n
     * @return
     */
    int F(int n) {
        if (n == 0) return 0;
        if (n == 1 || n == 2) return 1;
        return F(n - 1) + F(n - 2);
    }

    /**
     * 动态规划
     *
     * @param n
     * @return
     */
   static int F2(int n) {
        if (n < 1) {
            return -1;
        }
        int[] dp = new int[n];
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 1;
        for (int i = 3; i < n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n-1];
    }

(11) 旋转数组中最小数字

把一个数组最开始的若干个元素搬数组末尾,我们称之为数组的旋转

    /**
     * 判断旋转数组中的最小值
     * [3,4,5,6,0,1,2] mid>right
     * [1,0,1,1,1] mid == right
     * [2,2,3,4,5,6,6]mid > mid < right
     *
     * @param nums
     * @return
     */
    int minArray(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > right) {
                left = mid + 1;
            } else if (nums[mid] < right) {
                right = mid;
            } else {
                right--;
            }
        }
        return nums[left];
    }

(12)矩阵中的路径

给定一个mxn的二维字符网格board 和 一个字符串单词 word。如果 word存在于网格中,返回true;否则返回false。

回溯法模版:

backtrack(路径,选择列表) 
if(满足结束条件)
res.add(路径)
return

for(选择 in 选择列表)
做选择
backtrace(路径,选择列表)
撤销选择
 /**
     * 每一步可以在矩阵中向左、右、
     * 上、下移动一格。如果一条路径经过了矩阵的某一格
     * @param matrix 原字符数组
     * @param visit  已经访问过的标志
     * @return
     */
    // 表示上下左右对row和col的操作
    private final static int[][] next = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    boolean hasPath(char[][] matrix, boolean[][] visit, char[] target) {
        int row = matrix.length;
        int col = matrix[0].length;
        // 路径可以从矩阵中任意一格开始
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (findPath(matrix, visit, target, 0, i, j)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean findPath(char[][] matrix, boolean[][] visit, char[] target, int pathLength, int row, int col) {
        if (pathLength == target.length) {
            return true;
        }
        if (col < 0 || col > matrix[0].length || row < 0 || row > matrix.length
                || visit[row][col] || matrix[row][col] != target[pathLength]) {
            return false;
        }
        visit[row][col] = true;
        for (int[] i : next) {
            if (findPath(matrix, visit, target, pathLength + 1, row + i[0], col + i[1])) {
                return true;
            }
        }
        visit[row][col] = false;
        return false;
    }

(13)机器人的运动范围

地上有mxn个方格,机器人从[0][0] 运动到[m-1][n-1],每次可以上下左右移动一格,不能移动到方格外也不能进入行坐标和列坐标位数之和大于k的格子。


(14)打印二叉树

从左到右的顺序打印二叉树

    /**
     * 从左到右打印二叉树
     *
     * @param root
     * @return
     */
    int[] printOrderTreeNode(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<Integer> l = new ArrayList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode treeNode = queue.poll();
                l.add(treeNode.getVal());
                if (treeNode.left != null) {
                    queue.offer(treeNode.left);
                }
                if (treeNode.right != null) {
                    queue.offer(treeNode.right);
                }
            }
        }
        // j将list转为int数组
        int n = l.size();
        int j = 0;
        int[] res = new int[n];
        for (Integer i : l) {
            res[j++] = i;
        }
        return res;
    }

(15)打印二叉树

从左到右的顺序打印二叉树 每一层打印一行

 List<List<Integer>> printOrderTreeNode2(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<Integer> l = new ArrayList<>();
        queue.offer(root);
        List<List<Integer>> result = new ArrayList<>();
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 遍历完一层 加入列表
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode treeNode = queue.poll();
                list.add(treeNode.getVal());
                if (treeNode.left != null) {
                    queue.offer(treeNode.left);
                }
                if (treeNode.right != null) {
                    queue.offer(treeNode.right);
                }
            }
            result.add(list);
        }
        return result;
    }

(16)打印二叉树

从左到右的顺序打印二叉树之字形打印

List<List<Integer>> printOrderTreeNode3(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<Integer> l = new ArrayList<>();
        queue.offer(root);
        List<List<Integer>> result = new ArrayList<>();
        int temp = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 遍历完一层 加入列表
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode treeNode = queue.poll();
                list.add(treeNode.getVal());
                if (treeNode.left != null) {
                    queue.offer(treeNode.left);
                }
                if (treeNode.right != null) {
                    queue.offer(treeNode.right);
                }

            }
            if (result.size() % 2 == 1) Collections.reverse(list);
            result.add(list);
        }
        return result;
    }

(17) 剪绳子

长度为n的绳子,把绳子剪成整数长度的m段,每段绳子的长度计为k[0],k[1]…k[m-1].请问这些绳子的乘积最大为多少。

我们考虑动态规划

/**
     * 剪绳子的状态变化 -- 》 状态
     * dp[i]表示长度为i的绳子的最大乘积
     * 剪下一刀后剩下的两段长度是j和i-j,在这个上面还可能继续减(子问题)
     * dp[i] = max(dp[j]*dp[i-j])
     * base 条件 由于m>1,所以对于n=2和n=3,必须切一刀,返回1和2。
     *
     * @param
     * @return
     */
    int cuttingRop(int n) {
        if (n == 2) return 1;
        if (n == 3) return 2;
        int[] dp = new int[n + 1];
        //初始化
        dp[2] = 2;
        dp[3] = 3;
        for (int i = 4; i <=n;i++) {
            int max = 0;
            for (int j = 1;j<=1/2*i;j++) {
                max = Math.max(max,dp[i]*dp[i-j]);
            }
            dp[i] = max;
        }
        return dp[n];
    }

(18)最近最少使用算法

public class LRUCacheTest {
    int cap;
    
    // LRU 算法最近最少使用算法
    private LinkedHashMap<Integer,Integer> cache = new LinkedHashMap<>();

    public LRUCacheTest(int cap) {
        this.cap = cap;
    }
    
    public int get(int key) {
        if(!cache.containsKey(key)) {
            return -1;
        }
        // 将key变为最近使用
        makeRecently(key);
        return cache.get(key);
    }
    
    public void  put(int key,int val) {
        if(cache.containsKey(key)) {
            cache.put(key,val);
            makeRecently(key);
            return;
        }
        if(cache.size() == cap) {
            // 链表头部就是最进最少使用的;
            int oldKey = cache.keySet().iterator().next();
            cache.remove(oldKey);
        }
        cache.put(key,val);
    }

    private void makeRecently(int key) {
        // 将节点插入头部
        int val = cache.get(key);
        cache.remove(key);
        cache.put(key,val);
    }
}

(19) LFU 算法

(20) 序列化和反序列化二叉树

/**
 * 序列化和反序列化二叉树
 * 一般情况下需要前序+中序列
 * 中序+ 后序才可以重建一颗二叉树但由于
 * 我们序列化的时候确定了为null 的位置
 * 所以也是可以进行反序列化的
 */
public class SerializeOrDerialize {
    String SEP = "";
    String NULL = "#";

    String Serialize(TreeNode root) {
        StringBuilder stringBuilder = new StringBuilder();
        serTree(stringBuilder, root);
        return stringBuilder.toString();
    }

    private void serTree(StringBuilder stringBuilder, TreeNode root) {

        if (root == null) {
            stringBuilder.append(NULL).append(SEP);
            return;
        }
        stringBuilder.append(root.getVal()).append(SEP);
        serTree(stringBuilder, root.left);
        serTree(stringBuilder, root.right);
    }

    TreeNode deserialize(String data) {
        LinkedList<String> nodes = new LinkedList<>();
        for (String s : data.split(SEP)) {
            nodes.add(s);
        }
        return des(nodes);
    }

    private TreeNode des(LinkedList<String> nodes) {
        if(nodes.isEmpty()) return null;
        
        String first = nodes.removeFirst();
        if(first.equals(NULL)) return  null;
        TreeNode root = new TreeNode(Integer.parseInt(first));
        root.left = des(nodes);
        root.right = des(nodes);
        return root;
    }

}

(21) topN

 private static int[] getTopN(int[] array, int n) {
        if (array == null || n == 0) {
            return new int[]{};
        }
        PriorityQueue<Integer> priorityQueue = new PriorityQueue(array.length, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        for (int e : array) {
            priorityQueue.offer(e);
        }
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            res[i] = priorityQueue.poll();
        }
        return res;
    }

(22) 二叉搜索树的后续遍历

输入一个整数数组判断该数组是否为二叉搜索树的后续遍历结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。

 /**
     * 二叉树后续遍历的特征
     * 左子树的值小于根节点;右子树的值大于根节点;
     * 思路 先找到根节点再划分左右子树,
     *
     * @param nums
     * @return
     */
    private  boolean isPostOrder(int[] nums) {
        if(nums == null) return  false;
        return verifyPostOrder(nums, 0, nums.length - 1);
    }

    private static boolean verifyPostOrder(int[] nums, int start, int end) {

        int rootVal = nums[end];
        int i = start;
        for (; i < end; i++) {
            if (nums[i] > rootVal) {
                break;
            }
        }
        int j = i;
        for (; j < end; j++) {
            if (nums[j] < rootVal) {
                return false;
            }
        }
        boolean left = true;
        if (i > start) {
            left = verifyPostOrder(nums, start, i - 1);
        }
        boolean right = true;
        if (i < end) {
            right = verifyPostOrder(nums, i + 1, end);
        }

        return left && right;
    }

(23) 二叉树中和为某一值的路径

在这里插入代码片

(24) 复杂链表的复制

    /**
     * 1.首先将链表节点进行复制A->A'->B->B'->C->C'->NULL
     * 2.当前节点的随机指针进行复制(cur.next.random = cur.random.next;)
     * 3.将两个链表进行拆分
     *
     * @return
     */
    RandomListNode copyLinkedList(RandomListNode root) {

        RandomListNode cur = root;

        while (cur != null) {
            RandomListNode newNode = new RandomListNode(cur.val);
            newNode.next = cur.next;
            cur.next = newNode;
            cur = newNode.next;
        }
        //重置cur
        cur = root;
        while (cur != null) {
            if (cur.random != null) {
                cur.next.random = cur.random.next;
            }
            cur = cur.next.next;
        }

        // 拆分
        cur = root;
        RandomListNode head2 = root.next;
        while (cur != null) {
            RandomListNode temp = cur.next;
            cur.next = temp.next;
            if (temp.next == null ) {
                temp.next = null ;
            } else {
                temp.next = temp.next.next;
            }
            cur = cur.next;
        }
        return head2;
    }

(25)礼物的最大价值

dp数组多扩一列对可以减少对边界值的处理。入下图所示:那么dp[i][j]就相当于是grid[0][0]; 遍历也可以从下标1开始;而新增的这一行默认初始值为0;
在这里插入图片描述

    /**
     * 礼物的最大价值
     * dp[i][j] 表示走到i行j列拿到的礼物的最大累计价值
     * dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + grid[i][j]
     * @param grid
     * @return
     */
    int maxGiftValue(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dp = new int[m+1][n+1];
        for(int i = 1;i<m;i++) {
            for(int j =1 ;j<n;j++) {
                dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])  + grid[i-1][j-1];
            }
        }
        return dp[m][n];
    }

(26) 数据流中的中位数

   double midNum(int num) {
        PriorityQueue<Integer> queueMin = new PriorityQueue<>();
        PriorityQueue<Integer> queueMax = new PriorityQueue<>((o1, o2) -> o2 - o1);

        if (queueMin.size() == queueMax.size()) {
            queueMax.offer(num);
            queueMin.offer(queueMax.poll());
        } else {
            queueMin.offer(num);
            queueMax.offer(queueMin.poll());
        }
        if (((queueMin.size() + queueMax.size()) & 1) == 0) {
            return ( queueMin.peek() + queueMax.peek() ) /2.0;
        }
        return queueMin.peek();
    }

(27)




(28) 求 1+ 2 +3 …+n

int sum(int n ) {
        int s  = n ;
        boolean t = n > 0 &&  (s += sum(n-1)) > 0 ;
        return s;
    }

(29) 把字符串转换为整数


(30) 队列的最大值




(31)二叉搜索树中的第k大的数字

右中左的顺序遍历

    int ans = 0;
    private TreeNode  midSearch(TreeNode root,int k) {
        if(root == null) {
            return null;
        }
        // 遍历右子树
       midSearch(root.right,k);
        ans++;
        if(ans==k) {
            return root;
        }
        midSearch(root.left,k);
        return null;
    }

(32)不用加减乘除做加法


(33) 第一个只出现一次的字符

 private char currentFirst(String s) {
        HashMap<Character, Integer> currMap = new HashMap<>();
        for (int i = 0; i < s.length(); i++) {
            currMap.put(s.charAt(i),currMap.getOrDefault(s.charAt(i),0) +1);
        }
        for(Character c : currMap.keySet()) {
            Integer value = currMap.get(c);
            if(value == 1) {
                return c;
            }
        }
        return ' ';
    }
 private  char currentFirst2(String s) {
        if (s == null || s.length() == 0) {
            return ' ';
        }
        int[] count = new int[26];
        for (int i = 0; i < s.length(); i++) {
            int index = s.charAt(i) - 'a';
            count[index]++;
        }
        for(int j =0;j <s.length();j++) {
            int index = s.charAt(j) - 'a';
            if(count[index] == 1) {
                return s.charAt(j);
            }
        }
        return ' ';
    }

(34)两个链表的第一个公共节点

两个链表的第一个公共节点,定义双指针,指针1指向head1,指针1走到头之后指向head2;指针2走到头之后指向head1;相遇的节点就是公共节点;
  private ListNode someNode(ListNode node1, ListNode node2) {
        ListNode p1 = node1;
        ListNode p2 = node2;
        while (p1 != p2) {
            if (p1 != null) {
                p1 = p1.next;
            } else {
                p1=node2;
            }
            if (p2 != null) {
                p2 = p2.next;
            } else {
                p2=node1;
            }
        }
        return p1;
    }

(35)数组中数字出现的次数

两个相同数的异或结果为0;将数据中所有数字进行异或。最后就是两个不同数字异或的结果。

(36) 数组中数字出现的次数II


(37) 滑动窗口的最大值 (单调队列)

单调队列:输入数组nums和一个正整数k,有一个大小为k的窗口在nums上从左至右滑动,请输入每次滑动过程中滑动窗口的最大值。

int[] maxSlidingwindow(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k == 0) {
            return new int[];
        }
        int index = 0;
        int n = nums.length;
        int[] res = new int[n - k + 1];
        Deque<Integer> deque = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
                deque.pollLast();
            }
            deque.addLast(i);
            if (deque.peekFirst() == i - k) {
                deque.pollFirst();
            }
            if (i >= k - 1) {
                res[index++] = nums[deque.peekFirst()];
            }
        }
    }

(38)平衡二叉树

输入一颗二叉树的根节点,判断该树是不是平衡二叉树,如果某二叉树中任意节点的左右子树的深度不超过1,那么它就是一颗平衡二叉树;


 boolean isBlanceTree(TreeNode root) {
        if (root == null) {
            return true;
        }

        return (depth(root.left) - depth(root.right))<=1 && isBlanceTree(root.left) && isBlanceTree(root.right);
    }

    int depth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return 1 + Math.max(depth(root.left), depth(root.right));
    }

(39) 二叉搜索树的最近公共祖先

 TreeNode searchTreeParent(TreeNode root, TreeNode p, TreeNode q) {
        if( (p.val < root.val) && (q.val < root.val)) {
            return searchTreeParent(root.right,p,q);
        }
        if( (p.val > root.val) && (q.val >root.val)) {
            return searchTreeParent(root.right,p,q);
        }
        return root;
    }

(40) 二叉树的最近公共祖先

TreeNode commonTreeParent(TreeNode root,TreeNode p,TreeNode q) {
        if(root == null || root == p || root == q) return root;
        TreeNode left = commonTreeParent(root.left,p,q);
        TreeNode right = commonTreeParent(root.right,p,q);
        if(left == null) return right;
        if(right ==null) return left;
        return root;
    }

(41) 在排序数组中查找数字

统计一个数字在排序数组中出现的次数;
可以查找数字的左边界和右边界;注意:当我们while循环使用 <=时,左右边界需要做越界处理。

 int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int left = searchLeftIndex(nums, target);
        int right = searchRightIndex(nums, target);
        return right - left + 1;
    }

    int searchLeftIndex(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            }
        }
        if (left >= nums.length || nums[left] != target) {
            return 0;
        }
        return left;
    }

    int searchRightIndex(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            }
        }
        if (right <0 || nums[right] != target) {
            return 0;
        }
        return right;

    }

(42)最长公共子序列问题

LCS问题,最长公共子序列(子序列可以不连续);类似(25)最大礼物
这里也增加一个二维数组空间。由于两个字符串均为空串,或者其中一个为空的时候dp[0][j] 和 dp[i][0] 的值为0;所以多增加一个数组空间可以使代码更简洁;

    /**
     * dp[i][j] 表示字符串str1[0,...i-1] 和 str2[0,....j-1]
     * 的LCS长度
     * 当str[i] == str[j] 时
     * dp[i][j] = dp[i-1][j-1]+1
     * 否则 dp[i][j] = max(dp[i][j-1],dp[i-1][j])
     *
     * @param str1
     * @param str2
     * @return
     */
    int LongCommonSeq(String str1, String str2) {
        int m = str1.length();
        int n = str2.length();
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();
        int dp[][] = new int[m + 1][n + 1];
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (s1[i-1] == s2[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
                }
            }
        }
        return dp[m][n];
    }

(43) 把数组排列成最小的数

对于整数a,b,将其转换成字符串a,b后,比较字符串a+b和b+a的大小;
字符串相加正好是可以连接起来的,那么a+b就得到字符串32343,b+a就得到字符串43323,比较的是这两个字符串的大小,可以用ASCII码值进行比较,返回小的那个字符串排列,就相当于找到了数值的排列,这样整体排序完后就可以找到最小的数

String printMinNums(int[] nums) {
        if (nums == null || nums.length == 0) {
            return "";
        }
        List<String> res = new ArrayList<>();
        for (int num : nums) {
            res.add(String.valueOf(num));
        }
        res.sort((o1, o2) -> (o1 + o2).compareTo(o2 + o1));
        StringBuilder stringBuilder = new StringBuilder();
        for (String s : res) {
            stringBuilder.append(s);
        }
        return stringBuilder.toString();
    }

(44) 括号生成

数字 n 代表生成括号的对数,设计一个函数,用于能够生成所有可能的并且有效的括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

    List<String> result = new LinkedList<>();

    List<String> generateParenthesis(int n) {
        if (n == 0) {
            return result;
        }
        backtrace(n, n, "");
        return result;
    }

    private void backtrace(int left, int right, String s) {
        // 边界条件
        if (left < 0 || right < 0) {
            return;
        }
        // 剪枝
        if (left > right) {
            return;
        }
       // 符合条件的结果
       if (left == 0 && right == 0) {
            result.add(s);
        }

        /*回溯模板要遍历选择列表,但是本题的选择列表是两个独立的情况,所以单独写出来就好*/
        s = s + "(";
        backtrace(left - 1, right, s);
        s = s.substring(0, s.length() - 1);

        s = s + ")";
        // 进入下一层决策
        backtrace(left, right - 1, s);
        s.substring(0, s.length() - 1);
    }

(44)两数之和

public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                return new int[]{map.get(nums[i]), i};
            }
            map.put(target - nums[i], i);
        }
        return null;
    }

双指针法

public static List<List<Integer>> twoSum(int[] nums,int target) {
if(nums == null || nums.length == 0) {
     return null;
}
// 对数组进行排序
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int low = 0; int high = nums.length-1;
while(low<high) {
 int sum = nums[low] + nums[high];
 int lowValue = nums[low];
 int highValue = nums[right];
 if(sum < target) {
      while(low<high && lowValue == nums[low]) low++;  // 跳过重复元素
} else if(sum>target) {
      while(low<high && highValue == nums[high]) right--;  // 跳过重复元素
} else {
   List<Integer> temp = new ArrayList<>();
   temp.add(low);
   temp.add(high);
   res.add(temp);
   while(low<high && lowValue == nums[low]) low++;  // 跳过重复元素
   while(low<high && highValue == nums[high]) right--;  // 跳过重复元素
}
}
return res;
}

(45)三数之和

可以借助两数之和的结果 三数之和 a+b+c = target 等同于 a+b = target -c;

public List<List<Integer>> ThreeSum(int[] nums,int target) {
      
   for(int i=0;i<nums.length;i++) {
        List<List<Integer>> res = twoSum(nums,i+1,target-nums[i]);
        for(List<integer> list : res) {
           list.add(nums[i]);
        }
        // 跳过第一个数字
        while(i<n-1&& nums[i] == nums[i+1]) i++;
    }
}

public  List<List<Integer>> twoSum(int[] nums,int start,int target) {
        if (nums == null || nums.length == 0) {
            return null;
        }
        // 对数组进行排序
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        int low = start;
        int high = nums.length - 1;
        while (low < high) {
            int sum = nums[low] + nums[high];
            int lowValue = nums[low];
            int highValue = nums[high];
            if (sum < target) {
                while (low < high && lowValue == nums[low]) low++;  // 跳过重复元素
            } else if (sum > target) {
                while (low < high && highValue == nums[high]) high--;  // 跳过重复元素
            } else {
                List<Integer> temp = new ArrayList<>();
                temp.add(lowValue);
                temp.add(highValue);
                res.add(temp);
                while (low < high && lowValue == nums[low]) low++;  // 跳过重复元素
                while (low < high && highValue == nums[high]) high--;  // 跳过重复元素
            }
        }
        return res;
    }

(46)四数之和


同上

(47)k数之和

要求k数之和可以通过k-1数之和得到因此可以采用递归的方式求解

Arrays.sort(nums);
List<List<Integer>> nSum(int[] nums,n,start,target) {
  int size = nums.length;
  if(n<2 || size < n) return res;
  if(n==2) { // 两数之和}
  else {
     for(int i=start;i<size;i++) {
          List<List<Integer>> list = nSum(nums,n-1,i+1,target-nums[i]);
          for(List<integer> list : res) {
           list.add(nums[i]);
        }
     }
   }
   while(i<n-1&&nums[i] == nums[i+1]) i++;
}

(48)打家劫舍

public static int rob(int[] nums) {
        int len = nums.length;//记录数组长度
        if(len == 0)//若没有数据,直接返回0
            return 0;
        int[] dp = new int[len + 1];//建立dp数组
        dp[0] = 0;//初始化
        dp[1] = nums[0];
        for(int i = 2; i <= len; i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1]);//填入数据
        }
        return dp[len];//结果返回
    }

(50) 打家劫舍II

在这里插入图片描述

public int rob(int[] nums) {
    	if(nums==null||nums.length==0)
    		return 0;
    	if(nums.length==1)
    		return nums[0];
    	int[] dp1 = new int[nums.length];
    	int[] dp2 = new int[nums.length];
    	dp1[1] = nums[0]; //从第1个房屋开始偷
    	dp2[1] = nums[1]; //从第2个房屋开始偷
    	for(int i=2;i<nums.length;i++) {
    		dp1[i] = Math.max( dp1[i-2]+nums[i-1],dp1[i-1]);
    		dp2[i] = Math.max( dp2[i-2]+nums[i], dp2[i-1]);
    	}
    	return Math.max( dp1[nums.length-1], dp2[nums.length-1] );

(51)打家劫舍 III

树节点上进行的操作

HashMap<TreeNode,Integer> memo = new HashMap<>(); // 	备忘录
int rob(TreeNode root) {
    if(root == null) {
        return 0;
    }
    if(memo.containsKey(root)return memo.get(root);
    int do_it = root.val + (root.left == null ? 0 : rob(root.left.left)+rob(root.left.right)) + (root.right == null? 0 : rob(root.right.left) + rob(root.right.right));
   
    int notDo_it = rob(root.left) + rob(root.right);
    int res = Max.max(do_it,notDo_it);
    memo.put(root ,res);
    return res; 
}

(51)岛屿的最大面积



(52)被围绕的区域

给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

从边缘处为O的开始深度优先,所有相连的O都置为Q,这样深度优先结束之后剩下的O就是被X包围的了,然后再遍历每一个元素,将O变成X,将Q变成O还原。
void roundByX(int[][] borad) {
    if(borad == null || board.length==0 || board[0].length==0)
    {return;}
    int m = board.length;
    int n = board[0].length;
    for(int i = 0;i<m;i++) {
       for(int j = 0 ;j<n;j++) {
              if((i==0 || i==board.size()-1 || j==0 || j==board[0].size()-1) && board[i][j]=='O'){
                    core(board, i, j);
                }
       }
    }
    for(int i=0; i<board.size(); i++){
            for(int j=0; j<board[0].size(); j++){
                if(board[i][j] == 'O')
                    board[i][j] = 'X';
                if(board[i][j] == 'Q')
                    board[i][j] = 'O';
            }
        }
}

void core(int[][] board,int row,int col) {
if(board[i][j] == 'O'){
            board[i][j] = 'Q';
            //向上
            if(i > 0 && board[i-1][j] == 'O')
                core(board, i-1, j);
            //向下
            if(i < board.size()-1 && board[i+1][j] == 'O')
                core(board, i+1, j);
            //向左
            if(j > 0 && board[i][j-1] == 'O')
                core(board, i, j-1);
            //向右
            if(j < board[0].size()-1 && board[i][j+1] == 'O')
                core(board, i, j+1);
}

(53) 合并区间


(54)打开转盘锁


(55)单调栈 (next greater number)


(56) 跳跃游戏

贪心算法:思路就是每次都取最大的步数。条件fastest <=i 说明遇到0不能再跳跃了。

boolean canJump(int[] nums) {
        int n = nums.length;
        int fastest = 0;
        for (int i = 0; i < n-1; i++) {
            fastest = Math.max(fastest, i + nums[i]);
            if (fastest <= i) {
                return false;
            }
        }
        return fastest >= n - 1;
    }

(57)跳跃游戏II

如果保证可以跳到最后一格。最少跳多少次?
动态规划:找到状态、选择->明确dp函数含义->寻找状态之间的含义

// 贪心算法
int minJumpNum(int[] nums) {
        int n = nums.length;
        int fastest = 0 ; // 标记了[i.....end]之间能够跳到的最远距离
        int end = 0;
        int step = 0;
        for(int i=0;i<n-1;i++) {
            fastest = Math.max(fastest,i+nums[i]);
            if(end == i) {
                end = fastest;
                step++;
            }
        }
        return step;
    }

(58) 最长回文子串

从给定的字符串 s 中找到最长的回文子串的长度。
例如 s = “babbad” 的最长回文子串是 “abba” ,长度是 4 。

public String longestPalindrome(String s) {
    if (s == null || s.length() == 0)
        return s;
    int n = s.length();
    char[] str = s.toCharArray();

    String res = null;
    boolean[][] dp = new boolean[n][n];

    for (int i = n - 1; i >= 0; i--) {
        for (int j = i; j < n; j++) {
            dp[i][j] = str[i] == str[j] && (j - i <= 2 || dp[i + 1][j - 1]);
            if (dp[i][j] && (res == null || j - i + 1 > res.length())) {
                res = s.substring(i, j + 1);
            }
        }
    }
    return res;
}

(59) 最长回文子串II

1、布尔数组dp[i][j]表示子串s【i……j】是否为回文串

2、如何生成dp[i][j]?

2.1、当左右新加入的字符不同时,一定不为回文串,比如原串 “a” 左右新加字符后为“cab”,那这一定不为回文串,即dp[i][j]=false;

2.2、左右新加入的字符相同时,分以下两种讨论:

     2.2.1、原串长度<2,则此时一定为回文串,比如:原串为 “a”,加入后串变为“cac”,则dp[i][j]=true;

   2.2.2、原串长度>2,则加入两个相等字符看原串的值为false还是true。比如原串为“ac",此时原串对应的dp[i+1][j-1]=false,因为ac不是回文串,当加入了两个相同字符,比如为”bacb“,则dp[i][j]=dp[i+1][j-1]的值,即也为false;
public String longestPalindrome(String s) {
        int len=s.length();
        if(len<2)
        return s;
        char[] ch=s.toCharArray();
        boolean[][] dp=new boolean[len][len];
        for(int i=0;i<len;i++){
            for(int j=0;j<len;j++){
                dp[i][j]=true;
            }
        }
        int begin=0;
        int maxlen=1;
        for(int j=1;j<len;j++){
            for(int i=0;i<j;i++){
                if(ch[i]!=ch[j]){
                    dp[i][j]=false;
                }else{
                    if(j-i<3){
                        dp[i][j]=true;
                    }else{
                        dp[i][j]=dp[i+1][j-1];
                    }
                }
                if(j-i+1>maxlen&&dp[i][j]){
                            maxlen=j-i+1;
                            begin=i;
                        }
            }
        }
        return s.substring(begin,begin+maxlen);
    }

(60) 下一个更大元素 (单调栈、单调队列问题)


(61) 数组中是否有环

快慢指针,两指针相遇则有环

boolean hasCycle(ListNode head) {
    if(head == null) {
    return false;
    }
    ListNode fast = head;
    ListNode slow = head;
    while(fast !=null && fast.next!+null) {
     fast = fast.next.next;
     slow = slow.next;
     if(fast == slow) return true;
}
return false;
}

(62) 环形链表的环的入口

public ListNode detectCycle(ListNode head) {
         ListNode slow = head; //慢指针
        ListNode fast = head;//快指针
        if(head == null ){ //特判
           return null; 
        }
     //快指针每次走2步,慢指针每次都走一步
        while(fast != null && fast.next !=  null){
           
            fast = fast.next.next;
            slow = slow.next;
             if(fast == slow){
                break;
            }
        }
        //两个点相遇,将快慢指针随便一个扔回头结点,然后同时走,再次相遇就是链表入口节点
        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

(63) 最长上升子序列(子序列是不连续的)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
dp[i] 表示以index = i结尾的数组中最长上升子序列;
dp[i]=max(dp[j])+1;

public int lengthOfLIS(int[] nums) {
        int n=nums.length;
        if(n==0){
            return 0;
        }
        int maxlen=1;
        int[] arr=new int[n];
        Arrays.fill(arr,1);
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    arr[i]=Math.max(arr[i],arr[j]+1);
                }
            }
            maxlen=Math.max(arr[i],maxlen);
        }
        return maxlen;
    }

(64)无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。

public int lengthOfLongestSubstring(String s) {
 if (s.length() == 0) return 0;
        // 存放字符
        HashMap<Character, Integer> map = new HashMap<Character,Integer>();
        int max = 0; // 最大不重复字符长度
        int left = 0; // 上一个重复字符所在的位置(数组下标 + 1)
        for(int i = 0; i < s.length(); i++) {
            // 判断在map 中是否已经保存过字符
            if(map.containsKey(s.charAt(i))) {  
                // 取最大值: map.get(s.charAt(i)) + 1 表示上一个重复字符的位置
                left = Math.max(left, map.get(s.charAt(i)) + 1);
            }
            // 记录每个字符位置,如果重复则覆盖
            map.put(s.charAt(i), i);
            // 记录最大的不重复字符串长度。
            // (i - left + 1 ) == (i+1 - left) 表示:当前阶段最大的不重复字符串长度, + 1 的原因是 left 是从 1开始计算的 
            max = Math.max(max, i - left + 1);
        } 
        return max;
    }

(65)盛水最多的容器

双指针思路:定义左右两个指针。移动当前较低的才有可能比当前面积大。

public int maxArea(int[] height) {
        int res = 0;
        int left = 0;
        int right = height.length - 1;
        
        while (left < right) {
            res = Math.max(res, Math.min(height[left], height[right]) * (right - left));
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值