剑指offer个人笔记

本文部分方法图片来源于互联网上他人,这里纯属个人总结学习用,也希望能帮到其他小伙伴。

第 1 天 栈与队列(简单)

剑指 Offer 09. 用两个栈实现队列

  • 定义s1、s2两个栈来完成此任务
  • 在添加新元素阶段,直接添加即可
  • 在删除元素阶段,先判断s1是否为空,若为空,返回-1
  • 若不为空,则将s1逆置压入s2中
  • 将s2的顶元素退出
  • 再将s2元素逆置压入s1中
class CQueue
{
    Stack<Integer> s1;
    Stack<Integer> s2;
    public CQueue()
    {
        s1 = new Stack<Integer>();
        s2 = new Stack<Integer>();
    }

    public void appendTail(int value)
    {
        s1.push(value);
    }

    public int deleteHead()
    {
        if(s1.isEmpty())
        {
            return -1;
        }
        else
        {
            while (s1.isEmpty()==false)
            {
                s2.push(s1.pop());
            }
        }
        int res = s2.pop();
        while (s2.isEmpty()==false)
        {
            s1.push(s2.pop());
        }
        return res;
    }
}

剑指 Offer 30. 包含min函数的栈

  • 利用辅助栈来解决此问题
  • s1中保存所有元素,s2中按条件压入元素
  • 若s2为空,则压入元素。或者s2中的栈顶元素,大于待压入新元素,则压入新元素。保证s2中始终为递降序列。
  • 当返回min时,返回s2元素的栈顶元素。此时只返回栈顶值,而不退出栈顶元素。
  • java中stack类的peek()方法,实现此功能,只返回栈顶值,而不删除栈顶元素
  • 当pop时,要将s1的栈顶退出。同时也要判断,s1退出的栈顶元素,是否和s2目前的栈顶元素相同,若相同则退出s2栈顶。避免出现s2有多余元素。
class MinStack
{
    private Stack<Integer> s1;
    private Stack<Integer> s2;
    /** initialize your data structure here. */
    public MinStack()
    {
        s1 = new Stack<>();
        s2 = new Stack<>();
    }

    public void push(int x)
    {
        s1.push(x);
        if(s2.empty()||s2.peek()>=x)
        {
            s2.push(x);
        }
    }
    public void pop()
    {
         if(s1.pop().equals(s2.peek()))
             s2.pop();
    }

    public int top()
    {
        return s1.peek();
    }
    public int min()
    {
        return s2.peek();
    }
}

第 2 天 链表(简单)

剑指 Offer 06. 从尾到头打印链表

  • Collections.reverse(res);java中对list逆置的方法
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        if(head==null)
            return new int[0];

        List<Integer> res  = new ArrayList<>();
        while (head!=null)
        {
            res.add(head.val);
            head = head.next;
        }
        Collections.reverse(res);
        int [] ans = new int[res.size()];
        for(int i =0;i<res.size();i++)
        {
            ans[i] = res.get(i);
        }
        return ans;
    }
}

剑指 Offer 24. 反转链表

method1 辅助栈

  • 使用辅助栈,来存储链表中节点元素值
  • 再依次pop栈顶元素,替换对应节点的元素值
class Solution 
{
    public ListNode reverseList(ListNode head) 
    {
        if(head == null)
            return null;
        Stack<Integer> map = new Stack<>();
        ListNode temp = head;
        while (temp!=null)
        {
            map.add(temp.val);
            temp = temp.next;
        }
        ListNode cur = head;
        while (cur!=null)
        {
            cur.val = map.pop();
            cur = cur.next;
        }
        return head;
    }
}

method2 迭代

  • 定义两个指针,使用迭代的方法,依次调整节点的前后指向
class Solution 
{
    public ListNode reverseList(ListNode head) 
    {
        ListNode pre = null;
        ListNode cur = head;
        while (cur!=null)
        {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

method3 递归

class Solution
{
    public ListNode reverseList(ListNode head)
    {
        if(head == null||head.next==null)
            return head;
        ListNode newhead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return head;
    }
}

剑指 Offer 35. 复杂链表的复制

  • 哈希表解决此问题
  • 难点在于map.put(cur,new Node(cur.val));,key值不但是Node,value也是新的节点
  • map.get(cur).next = map.get(cur.next)中, map.get(cur.next)获取的是cur.next的对应节点,实际是map中key值为cur.next对应的value值,然后让map.get(cur).next(这里对应的是key为cur的value值)为前面提到的value值
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    public Node copyRandomList(Node head)
    {
        if(head==null)
            return null;
        //定义新指针,指向头结点
        Node cur = head;
        //定义哈希表
        Map<Node,Node> map = new HashMap<>();
        //从头到尾遍历,将每个节点,压入哈希表中,第一个值为指向该节点的指针,第二个值是该节点的值
        while (cur!=null)
        {
            map.put(cur,new Node(cur.val));
            cur = cur.next;
        }
        cur = head;
        //从头到位遍历
        while (cur!=null)
        {
            //map.get(cur.next)获得的是一个节点,然后将该节点赋给map.get(cur).next
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);

    }
}

第 3 天 字符串(简单)

剑指 Offer 05. 替换空格

class Solution {
    public String replaceSpace(String s) {
        char [] chars = s.toCharArray();
        String str = "";
        for(char c:chars)
        {
            if(c==' ')
                str=str+"%20";
            else
            {
                str = str+c;
            }
        }
        return str;
    }
}

剑指 Offer 58 - II. 左旋转字符串

class Solution 
{
    public String reverseLeftWords(String s, int n) 
    {
        String str = "";
        char [] chars = s.toCharArray();
        for(int i = n;i<s.length();i++)
        {
            str+=chars[i];
        }
        for(int i =0;i<n;i++)
        {
            str+=chars[i];
        }
        return str;
    }
}

第 4 天 查找算法(简单)

剑指 Offer 03. 数组中重复的数字

method1

  • 使用哈希表来解决此问题
class Solution {
    public int findRepeatNumber(int[] nums) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i:nums)
        {
            if(map.containsKey(i))
                return i;
            else
                map.put(i,1);
        }
        return -1;
    }
}

method2

  • 原地交换思想
class Solution {
    public int findRepeatNumber(int[] nums) {
        //使用原地交换的思想,解决此问题
        int i = 0;
        while (i<nums.length)
        {
            //若第i位置的值,为i本身,则continue
            if(nums[i] == i)
            {
                i++;
                continue;
            }
            //若nums[nums[i]] == nums[i]成立,意味着除过i位置之外,在其他位置还有元素值等于nums[i]
            if(nums[nums[i]] == nums[i]) return nums[i];
            //若上面条件不成立
            int temp = nums[i];
            nums[i] = nums[temp];
            nums[temp] = temp;
        }
        return -1;
    }
}

剑指 Offer 53 - I. 在排序数组中查找数字 I

  • 不错,这个题是我自己想出来的办法,继续加油
        //首先判断数组长度,若过短,就返回0
        if(nums.length==0)
            return 0;
        //判断是否出现,数组中不包含target的现象
        for(int n :nums)
        {
            //这里有两个判断
            //1 由于数组是非递减数组。数组前面的元素<=后面元素。那么当出现n>target情况出现,且判断2也没有使用上,说明数组中不包含target,返回0
            //2 若n==target时,则break
            if(n>target)
                return 0;
            else if(n==target)
                break;
        }
        //res保存结果
        int res = 0;
        for(int n :nums)
        {
            //当target出现,n++
            if(n == target)
                res++;
            //若res>0,说明target已出现,后续在出现不为target的元素,没必要再看,break即可。
            if(res>0&&n!=target)
                break;;
        }
        return res;

剑指 Offer 53 - II. 0~n-1中缺失的数字

解析

class Solution {
    public int missingNumber(int[] nums) {
        //排序数组中的搜索问题,首先想到 二分法 解决。
        int i = 0,j = nums.length-1;
        while (i<=j)
        {
            int m = (i+j)/2;
            if(nums[m] == m)i = m+1;
            else j = m-1;
        }
        return i;
    }
}

第 5 天 查找算法(中等)

剑指 Offer 04. 二维数组中的查找

  • 由于每一行从左到右递增,每一列从上到下递增
  • 因此target>nums[i][m],说明target不在此行中,换下一行寻找。这样提升了查找的效率
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        int n  = matrix.length;
        if(n==0)
            return false;
        int m =  matrix[0].length;
        if(m==0)
            return false;

        for(int i =0;i<n;i++)
        {
            if(matrix[i][m-1]<target)
            {
                continue;
            }
            else
            {
                for(int j = 0;j<m;j++)
                {
                    if(matrix[i][j]==target)
                        return true;
                }
            }
        }
        return false;
    }
}

剑指 Offer 11. 旋转数组的最小数字

method1

  • 取巧的方法,用java自带函数
class Solution {
    public int minArray(int[] numbers) {
        Arrays.sort(numbers);
        return numbers[0];
    }
}

method2
解析

    public int minArray(int[] numbers)
    {
        int i = 0,j = numbers.length-1;
        while (i<j)
        {
            int mid = (i+j)/2;
            if(numbers[mid]>numbers[j])
            {
                i = mid+1;
            }
            else if(numbers[mid]<numbers[j]) j = mid;
            else return findmin(numbers,i,j);
        }
        return numbers[i];
    }
    private int findmin(int [] nums,int start,int end)
    {
        int res = nums[0];
        for(int i =start;i<end;i++)
        {
            if(nums[i]<res) res = nums[i];
        }
        return res;
    }    

剑指 Offer 50. 第一个只出现一次的字符

  • hashmap解决问题
方法一
class Solution {
    public char firstUniqChar(String s) 
    {
        int n = s.length();
        if(n==0) return ' ';
        Map<Character,Integer> map = new HashMap<>();
        char [] chars = s.toCharArray();
        for (char c:chars)
        {
            if(map.containsKey(c)) map.put(c,map.get(c)+1);
            else map.put(c,1);
        }
        for (char c:chars)
        {
            if(map.get(c)==1) return c;
        }
        return ' ';       
    }
}
方法二
        int n = s.length();
        if(n==0) return ' ';
        Map<Character,Boolean> map = new HashMap<>();
        char [] chars = s.toCharArray();
        for(char c:chars)
        {
            map.put(c,!map.containsKey(c));
        }
        for(char c:chars)
        {
            if(map.get(c)) return c;
        }
        return ' ';

第 6 天 搜索与回溯算法(简单)

面试题32 - I. 从上到下打印二叉树

  • 定义一个队列,按照左右的顺序依次存储树节点
  • 再依次吐出,并存储吐出节点的左右节点
class Solution 
{
    public int[] levelOrder(TreeNode root) 
    {
        if(root==null) return new int[0];
        Queue<TreeNode> queue  = new LinkedList<>();
        queue.add(root);
        List<Integer> res = new ArrayList<>();
        while (!queue.isEmpty())
        {
            TreeNode node = queue.poll();
            res.add(node.val);
            if(node.left!=null) queue.add(node.left);
            if(node.right!=null) queue.add(node.right);
        }
        int [] ans = new int[res.size()];
        for(int i =0;i<res.size();i++)
            ans[i] = res.get(i);
        return ans;
    }
}

剑指 Offer 32 - II. 从上到下打印二叉树 II

  • 与上题一样,将节点保存至队列中
  • 如上题不同点在于,按行压入队列
  • int [][] res = {};java中定义空的二维数组
class Solution
{
    public List<List<Integer>> levelOrder(TreeNode root)
    {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if(root == null) return res;
        queue.add(root);
        while (!queue.isEmpty())
        {
            List<Integer> temp = new ArrayList<>();
            int n = queue.size();
            for(int i = 0;i<n;i++)
            {
                TreeNode node = queue.poll();
                temp.add(node.val);
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(temp);
        }
        return res;
    }
}

剑指 Offer 32 - III. 从上到下打印二叉树 III

class Solution
{
    public List<List<Integer>> levelOrder(TreeNode root)
    {
        //定义存储节点的队列
        Queue<TreeNode> queue = new LinkedList<>();
        //存储最终返回结果的多维列表
        List<List<Integer>> res = new ArrayList<>();
        //如果树为空,返回空res
        if(root==null) return res;
        queue.add(root);

        //由于希望按之字形打印二叉树,第一行从左到右,第二行从右到左,依次进行
        //定义是否翻转该层结果的标志
        //先将每一层还是按顺序存储
        //判断该层是否为奇数层,则不逆序,若为偶数层,逆序该层元素结果。达到之字形打印目的
        int flag = 1;
        while (!queue.isEmpty())
        {
            List<Integer> temp = new ArrayList<>();
            int n = queue.size();
            //遍历对每一层的节点
            for(int i = 0;i<n;i++)
            {
                //在吐出每一层节点的同时,将他们的子节点也压入
                TreeNode node = queue.poll();
                temp.add(node.val);
                if(node.left!=null) queue.add(node.left);
                if(node.right!=null) queue.add(node.right);
            }
            //若为偶数层,翻转序列
            if(flag%2==0) Collections.reverse(temp);
            flag++;
            res.add(temp);
        }
        return res;
    }
}

第 7 天 搜索与回溯算法(简单)

剑指 Offer 26. 树的子结构

解析

class Solution
{
    public boolean isSubStructure(TreeNode A, TreeNode B)
    {
        return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
    }
    private boolean recur(TreeNode A, TreeNode B)
    {
        if(B == null) return true;
        if(A == null || A.val != B.val) return false;
        return recur(A.left, B.left) && recur(A.right, B.right);
    }
}

剑指 Offer 27. 二叉树的镜像

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        //判断根节点是否为空,为空返回root
        if(root == null)return root;
        //递归式的,依次对二叉树的左右子树进行镜像化处理
        TreeNode left  =  mirrorTree(root.left);
        TreeNode right =  mirrorTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}

剑指 Offer 28. 对称的二叉树

解析

	class Solution
{
    public boolean isSymmetric(TreeNode root) {
        return root == null ? true : recur(root.left, root.right);
    }
    boolean recur(TreeNode L, TreeNode R) {
        if(L == null && R == null) return true;
        if(L == null || R == null || L.val != R.val) return false;
        return recur(L.left, R.right) && recur(L.right, R.left);
    }

}

第 8 天 动态规划(简单)

剑指 Offer 10- I. 斐波那契数列

class Solution {
    public int fib(int n) {
        if(n==0) return 0;
        int [] dp = new int[n+2];
        dp[0] = 0;
        dp[1] =1;
        for(int i =2;i<=n;i++)
        {
            dp[i] = dp[i-2]%1000000007+dp[i-1]%1000000007;
        }
        return dp[n]%1000000007;
    }
}

剑指 Offer 10- II. 青蛙跳台阶问题

class Solution {
    public int numWays(int n) {
        if(n==0) return 1;
        int [] dp = new int[n+2];
        dp[0] = 1;
        dp[1] = 1;
        for(int i =2;i<=n;i++)
        {
            dp[i] = dp[i-2]%1000000007+dp[i-1]%1000000007;
        }
        return dp[n]%1000000007;
    }
}

剑指 Offer 63. 股票的最大利润

class Solution
{
    public int maxProfit(int[] prices)
    {
        //cost存储最小花费
        int cost = Integer.MAX_VALUE,profit = 0;
        for(int price:prices)
        {
            //逐元素排查
            //找到最小的花费
            cost = Math.min(price,cost);
            //找到最小花费下的最大利润
            profit = Math.max(price-cost,profit);
        }
        return profit
    }
}

第 9 天 动态规划(中等)

剑指 Offer 42. 连续子数组的最大和

class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        //如果数组长度为1,返回nums[0]
        if(n==1) return nums[0];
        int max_sum =nums[0],cur = 0;
        for (int num:nums)
        {
            cur = Math.max(cur+num,num);
            max_sum = Math.max(max_sum,cur);
        }
        return max_sum;
    }
}

剑指 Offer 47. 礼物的最大价值

        int row = grid.length;
        int col = grid[0].length;
        //当数组的大小为0时,返回0
        if(row==0||col==0) return 0;
        int [][] dp = new int[row+1][col+1];
        for(int i =0;i<row;i++)
        {
            for (int j = 0;j<col;j++)
            {
                if(i==0&&j==0) continue;
                if(i==0)
                {
                    grid[i][j] = grid[i][j]+grid[i][j-1];
                    continue;
                }
                else if(j==0)
                {
                    grid[i][j] = grid[i][j]+grid[i-1][j];
                    continue;
                }
                grid[i][j] = Math.max(grid[i-1][j],grid[i][j-1])+grid[i][j];
            }
        }
        return grid[row-1][col-1]; 

第 10 天 动态规划(中等)

剑指 Offer 46. 把数字翻译成字符串

解析

class Solution
{
    public int translateNum(int num)
    {
        /*
        * f(i)为第i个位置总共有多少种表示可能
        * 若s[i-2:i]在10到25之间,则f(i) = f(i-1)+f(i-2)
        * 否则f(i) = f(i-1) */
        //将数字转为字符串
        String s = String.valueOf(num);
        //创建保存结果的数组
        int [] dp = new int[s.length()+1];
        //当字符串中只有一个数字时,值为1,因此dp[1] = 1;
        //dp[2]有两种可能,为2or1
        //dp[0]=1,就位dp[2]=2准备着,若条件符合则为2,否则为1
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2;i<s.length();i++)
        {
            //获得字符串的最后两位,看是否能组成一个字母
            String tmp = s.substring(i-2,i);
            if(tmp.compareTo("10")>=0&&tmp.compareTo("25")<=0)
            {
                dp[i] = dp[i-2]+dp[i-1];
            }
            else
            {
                dp[i] = dp[i-1];
            }
        }
        return dp[s.length()];
    }
}

第 11 天 双指针(简单)

剑指 Offer 18. 删除链表的节点

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head==null) return head;
        if(head.val == val)
        {
            head = head.next;
            return head;
        }
        ListNode pre = head;
        ListNode cur = pre.next;
        while (cur!=null)
        {
            if(cur.val==val)
            {
                pre.next = cur.next;
                break;
            }
            pre = pre.next;
            cur = cur.next;
        }
        return head;
    }
}

剑指 Offer 22. 链表中倒数第k个节点

class Solution 
{
    public ListNode getKthFromEnd(ListNode head, int k) 
    {
        //如果链表为空,返回null
        if(head==null) return null;
        //顺序遍历链表,统计链表长度
        int n = 0;
        ListNode count = head;
        while (count!=null)
        {
            n++;
            count = count.next;
        }
        //开始找到倒数第k个节点。如果i+k==n,说明此时是倒数k节点
        ListNode temp = head;
        int i = 0;
        while (temp!=null)
        {
            if(i+k==n) break;
            i++;
            temp = temp.next; 
        }
        return temp;
    }
}

第 12 天 双指针(简单)

剑指 Offer 25. 合并两个排序的链表

method1

class Solution 
{
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) 
    {
        ListNode cur = new ListNode(),dum = cur;
        cur = l1;
        if(l1 == null&&l2 == null)
        {
            return null;
        }
        else if(l1 == null&&l2!= null)
        {
            return l2;
        }
        else if(l1 != null&&l2 == null)
        {
            return l1;
        }
        while(cur!=null)
        {
            dum = cur;
            cur = cur.next;
        }
        dum.next = l2;
        cur = l1;
        dum = cur.next;

        while (cur!=null)
        {
            int temp = 0;
            dum = cur.next;
            while (dum!=null)
            {
                if(dum.val<cur.val)
                {
                    temp = cur.val;
                    cur.val = dum.val;
                    dum.val = temp;
                }
                dum=dum.next;
            }
            cur = cur.next;
        }
        return l1;

    }
}

method2

  • 双指针
class Solution 
{
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) 
    {
        if(l1==null&&l2==null) return null;
        ListNode dum = new ListNode(0), cur = dum;
        while(l1!=null&&l2!=null)
        {
            if(l1.val>l2.val)
            {
                cur.next = l2;
                l2 = l2.next;
            }
            else
            {
                cur.next = l1;
                l1 = l1.next;
            }
            cur = cur.next;
        }
        cur.next = (l1!=null?l1:l2);
        return dum.next;
    }
}

剑指 Offer 52. 两个链表的第一个公共节点

method1

  • 哈希表解决问题
public class Solution 
{
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) 
    {
        Map<ListNode,Integer>map = new HashMap<ListNode,Integer>();
        ListNode temp = headA;
        while (temp!=null)
        {
            map.put(temp,temp.val);
            temp = temp.next;
        }
        while (headB!=null)
        {
            if(map.containsKey(headB)==true)
            {
                return headB;
            }
            headB = headB.next;
        }
        return null;        
    }
}

method2
解析

public class Solution 
{
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) 
    {
        if(headA==null || headB == null)
            return null;
        ListNode pA = headA,pB = headB;
        while (pA!=pB)
        {
            pA = (pA == null?headB:pA.next);
            pB = (pB == null?headA:pB.next);
        }
        return pA;  
    }
}

第 13 天 双指针(简单)

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

  • 双指针解决此问题
class Solution 
{
    public int[] exchange(int[] nums) 
    {
        int n = nums.length;
        //如果数组长度为0,或1,返回
        if(n==0 || n==1) return nums;
        int i =0,j = n-1;
        while (i<j)
        {
            if(nums[i]%2==1) 
            {
                i++;
                continue;
            }
            else if(nums[j]%2==0)
            {
                j--;
                continue;
            }
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
        return nums;
    }
}

剑指 Offer 57. 和为s的两个数字

method1

  • 我的方法
class Solution 
{
    public int[] twoSum(int[] nums, int target) 
    {
        int n = nums.length;
        if(n==1) return nums;
        int [] res = new int[2];
        int j = n-1;
        while (nums[j]>=target)
        {
            j--;
        }
        for(int k = j;k>0;k--)
        {
            boolean flag = false;
            for(int i =0;i<k;i++)
            {
                if(nums[i]+nums[k]==target)
                {
                    res[0] = nums[i];
                    res[1] = nums[k];
                    flag = true;
                    break;
                }
                else if(nums[i]+nums[k]>target)
                {
                    break;
                }
            }
            if(flag==true)
                break;
        }
        return res;
    }
}

method2

  • 大佬的方法,对撞双指针 解析
class Solution 
{
    public int[] twoSum(int[] nums, int target) 
    {
        int n = nums.length;
        int i = 0,j = n-1;
        while (i<j)
        {
            int temp = nums[i]+nums[j];
            if(temp<target) i++;
            else if(temp>target) j--;
            else return new int[] {nums[i],nums[j]};
        }
        return new int[0];
    }
}

剑指 Offer 58 - I. 翻转单词顺序

解析

  • s.trim() 删除首尾空格
  • StringBuilder类
        s = s.trim(); // 删除首尾空格
        int j = s.length()-1,i=j;
        //当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
        //和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
        StringBuilder res = new StringBuilder();
        while (i>=0)
        {
            while (i>=0&&s.charAt(i)!=' ')i--;//找到第一个单词
            res.append(s.substring(i+1,j+1)+" ");//添加进结果中
            while (i>=0&&s.charAt(i)==' ')i--;//跳过两元素之间的空格
            j =i;
        }
        return res.toString().trim();//将StringBuilder转为String类,并去除首位多余空格

第 14 天 搜索与回溯算法(中等)

剑指 Offer 12. 矩阵中的路径

解析

class Solution {
    public boolean exist(char[][] board, String word)
    {
        char [] words = word.toCharArray();
        for(int i = 0;i<board.length;i++)
        {
            for(int j = 0;j<board[i].length;j++)
            {
                //利用深度搜索(DFS),判断矩阵是否包含目标单词
                if(dfs(board,words,i,j,0)) return true;
            }
        }
        return false;
    }
    private boolean dfs(char[][] board,char[] word,int i,int j,int k)
    {
        /*
        * 出现以下3种情况,则返回false
        * 当i的值大于等于数组宽,或i小于0
        * 当j的值大于等于数组长,或小于0
        * 或在board[i][j]!=word[k]
        */
        if(i>=board.length||i<0||j>=board[0].length||j<0||board[i][j]!=word[k]) return false;
        //若不为false,且k==word.length
        //表示数组已经满足条件
        //若k!=word.length,则继续寻找剩余的单词
        if(k == word.length-1) return true;
        //并将本轮已经查看过的board[i][j]置空
        board[i][j] = '\0';
        boolean res = dfs(board,word,i+1,j,k+1)||dfs(board,word,i,j+1,k+1)||dfs(board,word,i-1,j,k+1)||dfs(board,word,i,j-1,k+1);
        //对已经查看过的元素,我们让其恢复本来值
        board[i][j] = word[k];
        return res;
    }
}

剑指 Offer 13. 机器人的运动范围

class Solution
{
    int m,n,k;
    boolean[][] visited;
    public int movingCount(int m, int n, int k)
    {
        this.m = m;this.n = n;this.k = k;
        this.visited = new boolean[m][n];
        return dfs(0,0,m,n);
    }
    public int dfs(int i,int j,int m,int n)
    {
        if(i>=m||j>=n||visited[i][j]||bitSum(i)+bitSum(j)>k) return 0;
        visited[i][j] = true;
        return 1+dfs(i+1,j,m,n)+dfs(i,j+1,m,n);
    }
    private int bitSum(int n)
    {
        int sum = 0;
        while(n > 0) {
            sum += n % 10;
            n /= 10;
        }
        return sum;
    }
}

第 15 天 搜索与回溯算法(中等)

剑指 Offer 34. 二叉树中和为某一值的路径

解析

class Solution
{
    //保存最终结果
    LinkedList<List<Integer>> res = new LinkedList<>();
    //遍历每一个分支时,保存的结果
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target)
    {
        recur(root,tar);
        return res;
    }
    void recur(TreeNode root, int tar)
    {
        if (root == null) return;
        path.add(root.val);
        tar -= root.val;
        if (tar == 0 && root.left == null && root.right == null)
            res.add(new LinkedList(path));
        recur(root.left, tar);
        recur(root.right, tar);
        path.removeLast();
    }
}

剑指 Offer 54. 二叉搜索树的第k大节点

method1

  • 先将所有节点遍历
  • 再从所有节点中,找到需要的值
class Solution
{
    List<Integer> res = new ArrayList<>();
    public int kthLargest(TreeNode root, int k)
    {
        recur(root);
        Collections.sort(res);
        Collections.reverse(res);
        System.out.println(res);
        return res.get(k-1);
    }
    void recur(TreeNode root)
    {
        if(root==null) return;
        recur(root.left);
        res.add(root.val);
        recur(root.right);
    }
}

第 16 天 排序(简单)

剑指 Offer 61. 扑克牌中的顺子

method1

  • 哈希表
{
    public boolean isStraight(int[] nums)
    {
        //该数组元素为顺子的重复条件是,除过0以为,其它元素不能重复
        //同时max-min<5,如果最大值与最小值分别为 1,6  则返回false

        //定义Set集合,其中元素不重复
        Set<Integer> repeat = new HashSet<>();
        int max = 0,min = 15;
        for(int num:nums)
        {
            if(num==0) continue;
            max = Math.max(max,num);
            min = Math.min(min,num);
            if(repeat.contains(num)) return false;
            repeat.add(num);
        }
        return max-min<5;
    }
}

method2

  • 排序
class Solution 
{
    public boolean isStraight(int[] nums)
    {
        int joker = 0;
        Arrays.sort(nums);
        for(int i =0;i<4;i++)
        {
            if(nums[i]==0) joker++;
            else if(nums[i]==nums[i+1]) return false;
        }
        return nums[4]-nums[joker]<5;
    }
}

第 17 天 排序(中等)

剑指 Offer 40. 最小的k个数

method1

  • 使用java库函数,偷懒解决问题
class Solution
{
    public int[] getLeastNumbers(int[] arr, int k)
    {
        int n = arr.length;
        if (n==0) return new int[0];
        Arrays.sort(arr);
        return Arrays.copyOfRange(arr,0,k);
    }
}

剑指 Offer 41. 数据流中的中位数

method1

  • 暴力法
class MedianFinder {

    List<Integer> list = new ArrayList<>();
    /** initialize your data structure here. */
    public MedianFinder()
    {
        this.list = list;
    }
    public void addNum(int num)
    {
        list.add(num);
    }
    public double findMedian()
    {
        Collections.sort(list);
        int n = list.size();
        if(n==0) return 0.0;
        if(n%2==0)
        {
            return (list.get(n/2-1)+list.get(n/2))/2.0;
        }
        else
        {
            return list.get(n/2);
        }
    }
}

第 18 天 搜索与回溯算法(中等)

剑指 Offer 55 - I. 二叉树的深度

  • 递归解决此问题
class Solution
{
    public int maxDepth(TreeNode root)
    {
        if(root==null) return 0;
        return Math.max(recur(root.left)+1,recur(root.right)+1);
    }
    public int recur(TreeNode root)
    {
        if(root==null) return 0;
        return Math.max(recur(root.left)+1,recur(root.right)+1);
    }
}

剑指 Offer 55 - II. 平衡二叉树

*看不懂,挺难的

class Solution {
    public boolean isBalanced(TreeNode root)
    {
        return recur(root)!=-1;
    }
    private int recur(TreeNode root)
    {
        if(root==null) return 0;
        int left = recur(root.left);
        if(left==-1)return -1;
        int right = recur(root.right);
        if(right==-1) return -1;
        return Math.abs(left-right)<2?Math.max(left,right)+1:-1;
    }
}

第 19 天 搜索与回溯算法(中等)

剑指 Offer 64. 求1+2+…+n

  • 题目堵住了大多数现成方法
  • 因此使用逻辑运算&&的特性
  • A&&B中,如果A为非,则B不执行。若A为正,则B才执行。
题目要求为:求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
输入: n = 3
输出: 6
输入: n = 9
输出: 45
class Solution 
{
    public int sumNums(int n)
     {
        boolean flag = n>0&&(n+=sumNums(n-1))>0;
        return n;
    }
}

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

  • 二叉树为搜索树,即整棵树满足左子树节点的值<根节点<右子树节点的值
  • 使用遍历的思想解决此问题
  • 首先当root!=null进入循环,这里保证了root初始不为空的情况,若初始即为空,就返回null
  • 当p,q的val均大于root.val,说明p,q都处于root的右子树,则在root的右子树中找p,q
  • 当p,q的val均小于root.val,说明p,q都处于root的左子树,则在root的左子树中找p,q
  • 当不是以上两种情况是,说明找到最近公共祖先,break循环
class Solution 
{
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) 
    {
        while (root!=null)
        {
            if(root.val<p.val&&root.val<q.val)//p,q都在root的右子树
                root = root.right;//遍历root的右子节点
            else if(root.val>p.val&&root.val>q.val)//p,q都在root的左子树
                root = root.left;
            else break;//root.val == p.val || root.val == q.val
        }
        return root;
    }
}

第 21 天 位运算(简单)

剑指 Offer 15. 二进制中1的个数

  • n&0 = 0代表最后一位为0
  • n&1 = 1代表最后一位为1
  • java中无符号树右移一位n>>>=1;
public class Solution 
{
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) 
    {
        int res = 0;
        //n可能为正数或负数,因此此处while循环退出的条件为n!=0
        while (n!=0)
        {
            res+=n&1;
            n>>>=1;
        }
        return res; 
    }
}

剑指 Offer 65. 不用加减乘除做加法

class Solution
{
    public int add(int a, int b)
    {
        int sum = a^b;
        int carry = (a&b)<<1;
        while (carry!=0)
        {
            a = sum;
            b = carry;
            sum = a^b;
            carry = (a&b)<<1;
        }
        return sum;
    }
}

第 22 天 位运算(中等)

剑指 Offer 56 - II. 数组中数字出现的次数 II

  • 哈希表
class Solution 
{
    public int singleNumber(int[] nums) 
    {
        int n = nums.length;
        if(n==1) return nums[0];
        
        Map<Integer,Integer> map = new HashMap<>();

        for(int i = 0;i<n;i++)
        {
            if(!map.containsKey(nums[i]))
            {
                map.put(nums[i],1);
            }
            else
            {
                map.put(nums[i], map.get(nums[0])+1);
            }
        }
        for(int i =0;i<n;i++)
        {
            if(map.get(nums[i])==1)
            {
                return nums[i];
            }
        }
        return 0;
    }
}

动态规划

118. 杨辉三角

method1

  • 无脑版1
class Solution
{
    public List<List<Integer>> generate(int numRows)
    {
        int [][] res = new int[numRows][numRows];
        res[0][0] = 1;

        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> temp = new ArrayList<>();
        temp.add(1);
        ans.add(temp);
        if(numRows==1)
        {
            return ans;
        }
        res[1][0]=1;res[1][1] = 1;
        for (int i =2;i<numRows;i++)
        {
            res[i][0] = 1;
            for(int j = 1;j<i;j++)
            {
                res[i][j] = res[i-1][j]+res[i-1][j-1];
            }
            res[i][i] = 1;
        }
        for(int i = 1;i<numRows;i++)
        {
            List<Integer> t = new ArrayList<>();
            for(int j =0;j<=i;j++)
            {
                t.add(res[i][j]);
            }
            ans.add(t);
        }
        return ans;
    }
}

method2

  • 无脑版2
class Solution {
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> res = new ArrayList<>();
        for(int i =0;i<numRows;i++)
        {
            List<Integer> temp = new ArrayList<>();
            for(int j = 0;j<=i;j++)
            {
                if(j==0||j==i) temp.add(1);
                else
                {
                    temp.add(res.get(i-1).get(j)+res.get(i-1).get(j-1));
                }
            }
            res.add(temp);
        }
        return res;
    }
}

剑指 Offer 48. 最长不含重复字符的子字符串

  • 定义一个哈希表来完成此任务
  • 一个长度为N的字符串,其子字符串长度为(1+N)N/2
  • 遍历输入字符串s
class Solution 
{
    public int lengthOfLongestSubstring(String s) 
    {        
         Map<Character,Integer> map = new HashMap<>();
         int res = 0,tmp = 0;
         for(int j = 0;j<s.length();j++)
         {
             int i =  map.getOrDefault(s.charAt(j),-1);//获取索引i 如果该key值在map中,返回其位置,否则返回-1
             map.put(s.charAt(j),j);//更新哈希表
             tmp = tmp<j-i?tmp+1:j-i;
             res = Math.max(res,tmp);
         }
         return res;
    }
}

198. 打家劫舍

  • 该题的基本思想就是dp[i] = max(dp[i-2]+nums[i],dp[i-1])
  • 我与官方的基本思想完全一致,不同点在于dp[1]的取值,官方的思路是正确的
  • 我的错误点在于,没有在dp[1]部分应用,dp[i]的值是从开始到第i位置的最大合这一思想
官方
class Solution
{
    public int rob(int[] nums)
    {
        int n = nums.length;
        if(n == 1)
            return nums[0];
        int  [] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        for(int i = 2;i<n;i++)
        {
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[n-1];
    }
}
我的
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n == 1)
            return nums[0];
        int  [] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = nums[1];
        if(n==2)
            return Math.max(dp[1],dp[0]);
        for(int i = 2;i<n;i++)
        {
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[n-1];
    }
}

213. 打家劫舍 II

  • 与上一题相似,但是这里的问题是,第一间房子和最后一间房子只能选一个。
  • 因此在nums[0,n-1]与nums[1,n]中挑出最大的即可。使用到方法就是上一题的方法
class Solution {
    public int rob(int[] nums)
    {
        int n = nums.length;
        if(n==1)
            return nums[0];
        if(n==2)
            return Math.max(nums[0],nums[1]);
        return Math.max(MyRob(Arrays.copyOfRange(nums,0,nums.length-1)),MyRob(Arrays.copyOfRange(nums,1,n)));
    }
    private int MyRob(int [] nums)
    {
        int n = nums.length;
        if(n==1)
            return nums[0];
        int [] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        for(int i = 2;i<n;i++)
        {
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[n-1];
    }
}

55. 跳跃游戏

解析

  • [2,3,1,1,4]
  • 当从位置0开始,可以去到位置1或2,最远距离为2。若去到位置1,从位置1最远距离可以渠道1+3=4位置,也就是最后一位,因此该数组成立
  • [3,2,1,0,4]
  • 从位置0开始,最远可以去到下标为3,即值为0的位置,但是从位置3开始,不能在往下走。因此先去下标1处,从下标1处最远也是可以去下标3,因此该方法不行。尝试从下标2开始,从下标2也只能去下标3,因此本数组不成立。
  • 总之,若设能去到的最远距离为farest
  • farest = farest = Math.max(farest,i+nums[i]);
  • farest>=n-1,则成立
class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int farest = 0;
        for(int i = 0;i<n;i++)
        {
            if(i<=farest)
            {
                farest = Math.max(farest,i+nums[i]);
                if(farest>=n-1) return true;
            }
        }
        return false;
    }
}

45. 跳跃游戏 II

解析

class Solution 
{
    public int jump(int[] nums) 
    {
        int n = nums.length;
        int end = 0;
        int farest = 0;
        int steps = 0;
        for(int i =0;i<n-1;i++)
        {
            farest = Math.max(farest,i+nums[i]);
            if(i==end)
            {
                end = farest;
                steps++;
            }
        }
        return steps;
    }
}

53. 最大子数组和

class Solution
{
    public int maxSubArray(int[] nums)
    {
        int n = nums.length;
        //如果数组长度为1,返回nums[0]
        if(n==1) return nums[0];
        //dp数组存储结果
        int [] dp = new int[n];
        int max = nums[0];
        dp[0] = nums[0];
        for(int i = 1;i<n;i++)
        {
            //每个位置dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
            //Math.max(dp[i-1]+nums[i],nums[i])保证了当前位置的最大值,是连续的,或者是其元素值本身
            dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
            //使用max更新,最大的子数组和
            if(dp[i]>max) max = dp[i];
        }
        return max;
    }
}

918. 环形子数组的最大和

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int total = 0,maxSum = nums[0],curMax = 0, minSum = nums[0], curMin = 0;
        for(int a :nums)
        {
            curMax = Math.max(curMax+a,a);
            maxSum = Math.max(maxSum,curMax);
            curMin = Math.min(curMin+a,a);
            minSum = Math.min(minSum,curMin);
            total+=a;
        }
        return maxSum>0?Math.max(maxSum,total-minSum):maxSum;
    }
}

解析

122. 买卖股票的最佳时机 II

  • 如果数组中值是严格单调递增的,那么结果为num[0]-num[last] = 每两个相邻元素之差和
  • 否则的话,就看后一个元素是否大于前一个元素,若是则相减
    解析
class Solution 
{
    public int maxProfit(int[] prices) 
    {
        int profit = 0;
        for(int i=1;i<prices.length;i++)
        {
            int temp  = prices[i]-prices[i-1];
            if(temp>0) profit+=temp;
        }
        return profit;
    }
}

第16天 排序(简单)

剑指 Offer 45. 把数组排成最小的数

  • 对数组arr进行排序
  • 将结果保存数组res中
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int [] res = new int[k];
        //java自带排序算法
        Arrays.sort(arr);
        for(int i = 0;i<k;i++)
        {
            res[i] = arr[i];
        }
        return res;
    }
}

121. 买卖股票的最佳时机

  • 找到最小cost
  • 找到最大price
  • 两者相减即为最大利润
class Solution
{
    public int maxProfit(int[] prices)
    {
        int n = prices.length;
        if(n==1) return 0;
        int cost = prices[0],profit = 0,temp = 0;
        for(int price:prices)
        {
            cost = Math.min(cost,price);
            temp = price-cost;
            profit = Math.max(temp,profit);
        }
        return profit;
    }
}

第 18 天 搜索与回溯算法(中等)

剑指 Offer 55 - I. 二叉树的深度

  • 后序遍历
  • 若root为空返回0
  • 返回每个子树的深度,将最大子树深度值加1,即就是二叉树的深度值
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;
        return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
    }
}

剑指 Offer 55 - II. 平衡二叉树

  • 若root为空返回true
  • 判断从根节点为启示,左右子树是否为平衡二叉树
  • 再分布判断左右子树内部是否也为平衡二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root)
    {
        if(root == null) return true;
        return Math.abs(maxDepth(root.left)-maxDepth(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
    }
    public int maxDepth(TreeNode root)
    {
        if(root == null) return 0;
        return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
    }
}

杂项

计算最大公约数
method1

    static int gcd(int a, int b)
    {
        return b == 0 ? a : gcd(b, a % b);
    }

method2

    static int gcd(int a, int b)
    {
        if(b == 0)
            return a;
        while (a%b!=0)
        {
            int temp = a%b;
            a = b;
            b= temp;
        }
        return b;
    }

最小公倍数

    public static int lcm(int a,int b)
    {
        return a*b/gcd(a,b);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值