数据结构算法题日志

Lecode 算法。

链接:https://leetcode.cn
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1.(53) 最大子数组和

题目:
在这里插入图片描述
解题思路:
   考虑数组中的元素是单独成一段还是加入之前的那一段,故比较该元素于前一段组合大小,取大者即可。
java代码如下所示:
在这里插入图片描述
** java求最大值Math.max**

2.(1)两数之和

题目:
在这里插入图片描述

解题思路:
(1)暴力破解
在这里插入图片描述
注意:(1)nums长度为nums.length
(2)return 数组为return new int[]{i,j}

3(88)合并两个有序数组

题目:
在这里插入图片描述
解题方法:
(1)使用sort内置函数
在这里插入图片描述
**注:Arrays.sort()为内置Array函数
(2)双指针

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = 0, p2 = 0;
        int[] sorted = new int[m + n];
        int cur;
        while (p1 < m || p2 < n) {
            if (p1 == m) {
                cur = nums2[p2++];
            } else if (p2 == n) {
                cur = nums1[p1++];
            } else if (nums1[p1] < nums2[p2]) {
                cur = nums1[p1++];
            } else {
                cur = nums2[p2++];
            }
            sorted[p1 + p2 - 1] = cur;
        }
        for (int i = 0; i != m + n; ++i) {
            nums1[i] = sorted[i];
        }
    }
}

(4)(350)两个数组的交集II

题目:
在这里插入图片描述
解题思路:
(1)哈希表
    由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
代码如下所示:

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        // 挑选出两个数组找个短的那个
        if (nums1.length > nums2.length) {
            return intersect(nums2, nums1);
        }

        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        // nums1 = [4,9,5], nums2 = [9,4,9,8,4]
        for (int num : nums1) {
            // map.getOrDefault : 存在这个数就返回,不存在就返回默认值
            int count = map.getOrDefault(num, 0) + 1;
            map.put(num, count);
        }
        // 开辟一块内存空间用来存放两个数组的交集
        int[] intersection = new int[nums1.length];
        int index = 0;
        for (int num : nums2) {
            // num1中不存在这个数就在map中添加num=0
            int count = map.getOrDefault(num, 0);
            // 存在这个数就往后执行
            if (count > 0) {
                // 把这个数填充到数组中
                intersection[index++] = num;
                // 计数减一
                count--;
                // 如果还大于0
                if (count > 0) {
                    // 再次添加进去,覆盖之前那个key
                    map.put(num, count);
                } else {
                    // 不大于0移除这个数
                    map.remove(num);
                }
            }
        }
        // public static int[] copyOfRange(int[] original, int from, int to)
        // 对已有([9, 4, 0])的数组进行截取和赋值,结果为[9,4]
        return Arrays.copyOfRange(intersection, 0, index);
    }
}

注意:(1)Map的用法:Map.getOrDefault(key,默认值);
Map中会存储一一对应的key和value。
如果 在Map中存在key,则返回key所对应的的value。
如果 在Map中不存在key,则返回默认值。
(2)Arrays的用法:Arrays.copyOfRange(T[] original, int from, int to)复制from到to的元素。

(5)(121)买卖股票的最佳时机

题目为:
在这里插入图片描述
解题思路:
(1)暴力破解:会超时;
(2)一次遍历:记录当前遍历最小值,如果当前值小于最小值,替换最小值,如何不小于,计算差值后于最大利润比较,若大于,则替换最大利润。
代码如下所示:

public class Solution {
    public int maxProfit(int prices[]) {
        int minprice = Integer.MAX_VALUE;# Integer.MAX_VALUE表示int数据
        类型的最大取值数:2 147 483 647
		Integer.MIN_VALUE表示int数据类型的最小取值数:-2 147 483 648
        int maxprofit = 0;
        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minprice) {
                minprice = prices[i];
            } else if (prices[i] - minprice > maxprofit) {
                maxprofit = prices[i] - minprice;
            }
        }
        return maxprofit;
    }
}


注意: Integer.MAX_VALUE表示int数据类型的最大取值数:2 147 483 647
Integer.MIN_VALUE表示int数据类型的最小取值数:-2 147 483 648

(6)(118)杨辉三角

题目如下所示:
在这里插入图片描述
解题思路:
代码如下:

class Solution {
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> ret = new ArrayList<List<Integer>>();
        for (int i = 0; i < numRows; ++i) {
            List<Integer> row = new ArrayList<Integer>();
            for (int j = 0; j <= i; ++j) {
                if (j == 0 || j == i) {
                    row.add(1);
                } else {
                    row.add(ret.get(i - 1).get(j - 1) 
                    + ret.get(i - 1).get(j));//将上一级的数进行两两相加
                }
            }
            ret.add(row);
        }
        return ret;
    }
}

(7)36.有效的数独

题目如下所示:
在这里插入图片描述
解题思路:
用哈希表记录每一行、每一列、每一个小九宫格中,每个数字出现的次数。后判断对应条件即可。代码如下所示:

class Solution {
    public boolean isValidSudoku(char[][] board) {
        int[][] rows = new int[9][9];
        int[][] columns = new int[9][9];
        int[][][] subboxes = new int[3][3][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                char c = board[i][j];
                if (c != '.') {
                    int index = c - '0' - 1; // 字符转int,计算该c为多少,ASCII码计算
                    rows[i][index]++;
                    columns[j][index]++;
                    subboxes[i / 3][j / 3][index]++; // 判别为第几个方块里出现的数字。
                    if (rows[i][index] > 1 || columns[j][index] > 1 || subboxes[i / 3][j / 3][index] > 1) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

(8)73.矩阵置零

题目如下所示:
在这里插入图片描述
注:原地算法:一个原地算法(in-place algorithm)是一种使用小的,固定数量的额外之空间来转换资料的算法。当算法执行时,输入的资料通常会被要输出的部分覆盖掉。
解题思路:
(1)普通标记数组,占用O(M+N)
设定行标和列标,当有0,置1.后更新全部。
代码如下所示:

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean[] row = new boolean[m];
        boolean[] col = new boolean[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = col[j] = true;
                }
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (row[i] || col[j]) {
                    matrix[i][j] = 0;
                }
            }
        }
    }
}

(2)使用两个标记变量
其第一行第一列用于判断剩余行列是否存在0;
代码如下所示:

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean flagCol0 = false, flagRow0 = false;
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                flagCol0 = true;
            }
        }
        for (int j = 0; j < n; j++) {
            if (matrix[0][j] == 0) {
                flagRow0 = true;
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        if (flagCol0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
        if (flagRow0) {
            for (int j = 0; j < n; j++) {
                matrix[0][j] = 0;
            }
        }
    }
}

(9) 387.字符串中的第一个唯一字符

题目如下所示:
在这里插入图片描述
解题思路:
(1)使用哈希表存储频数
代码如下所示:

class Solution {
    public int firstUniqChar(String s) {
        Map<Character, Integer> frequency = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); ++i) {
            char ch = s.charAt(i);
            frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
        }
        for (int i = 0; i < s.length(); ++i) {
            if (frequency.get(s.charAt(i)) == 1) {
                return i;
            }
        }
        return -1;
    }
}

注意:String中操作charAt(),此方法返回这个字符串的指定索引处的char值

(10)383 赎金信

题目如下所示:
在这里插入图片描述
解题思路:进行字符统计,使用Map或者int[26] 进行相关的统计。代码如下所示:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
            Map<Character,Integer> map=new HashMap<Character,Integer>();
            for(int i=0;i<ransomNote.length();i++){
                char a=ransomNote.charAt(i);
                map.put(a,map.getOrDefault(a,0)+1);
            }
            for(int i=0;i<magazine.length();i++){
                char b=magazine.charAt(i);
                map.put(b,map.getOrDefault(b,0)-1);
            }
            for (Map.Entry<Character, Integer> entry : map.entrySet()) {
                int pos = entry.getValue();
                if(pos>0){
                    return false;
                }
    }
    return true;
}
}

也可以如下所示:

public boolean canConstruct(String ransomNote, String magazine) {
    int[] arr = new int[26];
    for (int i = 0; i < magazine.length(); i++)
        arr[magazine.charAt(i) - 'a']++;
    for (int i = 0; i < ransomNote.length(); i++)
        if (arr[ransomNote.charAt(i) - 'a']-- == 0) return false;
    return true;
}

注:(1)map的用法:java中获取map中key和value的方式有两种:
map.keySet() : 先获取map中的key,然后根据key获取value。
map.entrySet() : 获取map中的key和value,只需查询一次。
ToCharArray( )的用法,将字符串对象中的字符转换为一个字符数组char[]。
(2)题目为小写字母,故可以分配26个空间来进行相关字符的计数实现。

(11)141 环形链表

题目如下所示:
在这里插入图片描述
解题思路:
(1)使用哈希表来存储已经遍历过的结点
代码如下所示:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> a=new HashSet<ListNode>();
        while(head!=null){
            if(!a.add(head)){
                return true;
            }
            head=head.next;
        }
        return false;
    }
}

(2)Floyd 判圈算法(龟兔赛跑算法)
围绕成一个圈,双指针,分别按不同速率前进,若最终兔子能超过乌龟,则说明有圈。
代码如下所示:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) { //防止初始后序为空
            return false;  
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

注:(1)|和||的差别:
||:短路或。当符号"||"左边程序为真(true)后。结果就为true。不去运算符号右边程序。如果左边程序为false,运算右边程序,右边程序为true,结果为true。符号左右两边有一个为true,结果为true。
|:符号左右程序都会运算。两边都为true,结果为true。两边有一个为false,则结果为false;符号左右两边有一个为true,结果为true。

(2)定义链表ListNode时,

  • 链表的首个值不能为0,当首个参数为0时,代表着链表为空。 只需要定义一个ListNode xx = newListNode(0);即可。即只定义一个空链表。 不需要定义长度 。
  • 赋值时 通过xx.next = newListNode(4);来赋值,注意此时是赋值给下一个指针指向的位置,此时此链表一个值,值为4。
  • 取第一个值时,只需要xx.val即可。 取第二或之后的值时,需要xx = xx.next;int x = xx.val;这个方式取值。

(12)21.合并两个有序链表

题目如下所示:
在这里插入图片描述
解题思路:
(1)递归
两个链表头部值较小的与另一个剩下元素操作结果合并。
代码如下所示:

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

(2)迭代
设置哨兵结点,比较l1和l2,进行合并。
代码如下所示:

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);
        ListNode prev = prehead;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                prev.next = l1;
                l1 = l1.next;
            } else {
                prev.next = l2;
                l2 = l2.next;
            }
            prev = prev.next;
        }

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev.next = l1 == null ? l2 : l1;

        return prehead.next;
    }
}

注意:创建链表哨兵头可以为:ListNode pre = new ListNode(-1);
ListNode prehead = pre;

(13)移除链表元素

题目如下所示:
在这里插入图片描述
解题思路:
(1)递归
递归的终止条件是 head 为空,此时直接返回 head。当 head 不为空时,递归地进行删除操作,然后判断 head 的节点值是否等于 val并决定是否要删除 head。
代码如下所示:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;
        }
        head.next = removeElements(head.next, val);
        return head.val == val ? head.next : head;
    }
}

(2)迭代
由于链表的头节点 head 有可能需要被删除,因此创建哑节点 dummyHead,令 dummyHead.next=head,初始化 temp=dummyHead,然后遍历链表进行删除操作。最终返回 dummyHead.next 即为删除操作后的头节点。
代码如下所示:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode temp = dummyHead;
        while (temp.next != null) {
            if (temp.next.val == val) {
                temp.next = temp.next.next;
            } else {
                temp = temp.next;
            }
        }
        return dummyHead.next;
    }
}

(14)206反转链表

题目如下所示:
在这里插入图片描述
解题思路为:
(1)迭代
在遍历链表时,将当前节点的 next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
代码如下所示:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

(2)递归
n k.next.next=nk
需要注意的是 n1的下一个节点必须指向 ∅。如果忽略了这一点,链表中可能会产生环。
代码如下所示:

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 newhead;
    }
}

(15)83删除排序链表中的重复元素

题目如下所示:
在这里插入图片描述
解题思路如下:
一次遍历:
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。
代码如下所示:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
            ListNode result=head;
            while(head!=null&&head.next!=null){
                if(head.val==head.next.val){
                    head.next=head.next.next;
                }else{
                    head=head.next;
                }
            }
            return result;
    }
}

(16)有效的括号

题目如下所示:
在这里插入图片描述
解题思路:
使用栈这一数据结构来解决。
我们遍历给定的字符串 sss。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
代码如下所示:

class Solution {
    public boolean isValid(String s) {
        int n = s.length();
        if (n % 2 == 1) {
            return false;
        }

        Map<Character, Character> pairs = new HashMap<Character, Character>() {{
            put(')', '(');
            put(']', '[');
            put('}', '{');
        }};
        Deque<Character> stack = new LinkedList<Character>();
        for (int i = 0; i < n; i++) {
            char ch = s.charAt(i);
            if (pairs.containsKey(ch)) {
                if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
                    return false;
                }
                stack.pop();
            } else {
                stack.push(ch);
            }
        }
        return stack.isEmpty();
    }
}

注:(1)Java中Stack栈中的 peek()方法 和 pop()方法 的区别
stack.peek() 的作用是返回栈顶元素,但不是将栈顶元素弹出。
stack.pop() 的作用是返回栈底元素,并且同时将栈顶元素出栈。
(2)新建一个栈:Deque stack = new LinkedList();
从官方解释来看,ArrayDeque是无初始容量的双端队列,LinkedList则是双向链表。

(17)232 用栈实现队列

题目如下所示:
在这里插入图片描述
解题思路:
将一个栈当作输入栈,用于压入 push传入的数据;另一个栈当作输出栈,用于 pop 和 peek操作。
每次 pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。
代码如下所示:

class MyQueue {
    Deque<Integer> inStack;
    Deque<Integer> outStack;

    public MyQueue() {
        inStack = new ArrayDeque<Integer>();
        outStack = new ArrayDeque<Integer>();
    }

    public void push(int x) {
        inStack.push(x);
    }

    public int pop() {
        if (outStack.isEmpty()) {
            in2out();
        }
        return outStack.pop();
    }

    public int peek() {
        if (outStack.isEmpty()) {
            in2out();
        }
        return outStack.peek();
    }

    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    private void in2out() {
        while (!inStack.isEmpty()) {
            outStack.push(inStack.pop());
        }
    }
}

注:
在这里插入图片描述

(18)二叉树的前序遍历

题目如下所示:
在这里插入图片描述
解题思路:
(1)递归
可以显而易见,可以使用递归的方法进行解决。
代码如下所示:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        preorder(root, res);
        return res;
    }

    public void preorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        preorder(root.left, res);
        preorder(root.right, res);
    }
}

(2)迭代
代码如下所示:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return res;
    }
}

(19)二叉树的后序遍历

题目如下所示:

在这里插入图片描述
解题思路:
(1)递归
(2)迭代(难)
代码如下所示:

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }

        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode prev = null;
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            if (root.right == null || root.right == prev) {
                res.add(root.val);
                prev = root; //用于辨别是否重复添加,防止循环
                root = null; // 防止重复增添左结点
            } else {
                stack.push(root);
                root = root.right;
            }
        }
        return res;
    }
}

(20)二叉树的层序遍历

题目如下所示:
在这里插入图片描述
解题思路:
(1)BFS(广度优先算法)
广度优先遍历是按层层推进的方式,遍历每一层的节点。
代码如下所示:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ret = new ArrayList<List<Integer>>();
        if (root == null) {
            return ret;
        }

        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            List<Integer> level = new ArrayList<Integer>();
            int currentLevelSize = queue.size();
            for (int i = 1; i <= currentLevelSize; ++i) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            ret.add(level);
        }
        
        return ret;
    }
}

(2)DFS(深度优先算法)
递归的思想。
代码如下所示:

import java.util.*;	
class Solution {
	public List<List<Integer>> levelOrder(TreeNode root) {
		if(root==null) {
			return new ArrayList<List<Integer>>();
		}
		//用来存放最终结果
		List<List<Integer>> res = new ArrayList<List<Integer>>();
		dfs(1,root,res);
		return res;
	}
	
	void dfs(int index,TreeNode root, List<List<Integer>> res) {
		//假设res是[ [1],[2,3] ], index是3,就再插入一个空list放到res中
		if(res.size()<index) {
			res.add(new ArrayList<Integer>());
		}
		//将当前节点的值加入到res中,index代表当前层,假设index是3,节点值是99
		//res是[ [1],[2,3] [4] ],加入后res就变为 [ [1],[2,3] [4,99] ]
		res.get(index-1).add(root.val);
		//递归的处理左子树,右子树,同时将层数index+1
		if(root.left!=null) {
			dfs(index+1, root.left, res);
		}
		if(root.right!=null) {
			dfs(index+1, root.right, res);
		}
	}
}

注:
在这里插入图片描述
1、add()和offer()区别:
add()和offer()都是向队列中添加一个元素。一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false。因此就可以在程序中进行有效的判断!
2、poll()和remove()区别:
remove() 和 poll() 方法都是从队列中删除第一个元素。如果队列元素为空,调用remove() 的行为与 Collection 接口的版本相似会抛出异常,但是新的 poll() 方法在用空集合调用时只是返回 null。因此新的方法更适合容易出现异常条件的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值