大型互联网公司最常见的leetcode编程题

LeetCode经典算法解析
本文精选LeetCode上的经典算法题目,包括字符串全排列、链表排序、括号匹配、区块计算、编码组合及矩阵字符串匹配等问题,深入解析了回溯、动态规划、深度遍历等算法思想,并提供了详细的代码实现。

2019.9.30

第一题(leetcode 46. Permutations,求字符串的全排列)

leetcod46,求字符串的全排列

对数组字符串等子集的排列组合题目的汇总归纳

对字符串进行全排列的问题经常出现在各大公司的面试题上,属于必须要掌握的题。
题目描述:
46. Permutations
Medium

Given a collection of distinct integers, return all possible permutations.

Example:

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
import java.util.*;

import static leetcode.ImprotantSubsets.swap;

public class permute {
    public static void main(String[] args) {
        int[] array = {1,2,3};
        List<List<Integer>> permute = permute(array);
        for (List list:permute){
            Iterator iterator = list.iterator();
            while (iterator.hasNext()){
                System.out.print(iterator.next() + " ");
            }
            System.out.println();
        }
    }

    public static List<List<Integer>> permute(int[] nums) {
            List<List<Integer>> result = new ArrayList<>();
            HashSet<List<Integer>> set = new HashSet<>();
            //利用回溯法,将nums数组的元素都重新排序并装入set集合中,消除掉了重复的元素
            Resort(nums,set,0);
            // 利用集合的addAll方法,将某个集合的元素复制到本集合内
            result.addAll(set);
            return result;
    }

    private static void Resort(int[] nums, HashSet<List<Integer>> set, int k) {
        if (k == nums.length - 1) {
            // 这里需要new 一个list 来接收nums数组的元素
            List<Integer> list = new ArrayList<>();
            for (int n: nums
                 ) {
                list.add(n);
            }
            set.add(list);
        }
        // 关键的步骤,i = k不是i = 0;
        for (int i = k; i < nums.length; i++) {
            swap(nums,i,k);
            // 第三个参数要是k+1,而不是i+ 1,k
            Resort(nums,set,k+1);
            swap(nums,k,i);
        }



    }

第二题(leetcode 148. Sort List,给链表排序)

leetcode 148. Sort List


题目描述
148. Sort List
Medium

Sort a linked list in O(n log n) time using constant space complexity.

Example 1:

Input: 4->2->1->3
Output: 1->2->3->4
Example 2:

Input: -1->5->3->4->0
Output: -1->0->3->4->5

第一种方法,直接用优先队列排好序,然后重建链表,笔试的时候优先选简单的方法
//time complexity O(nlogn) space compexity O(n)

   public ListNode sortList2(ListNode head) {
        if(head==null) {
            return null;
        }
	//利用优先队列将链表中的数进行排序
        PriorityQueue<Integer> q = new PriorityQueue<Integer>();
        ListNode cur = head;
        ListNode dummy = cur;
        while(head!=null){
            q.add(head.val);
            head = head.next;
        }
        //重建链表
        while(q!=null&&cur!=null){
            cur.val = q.poll();
            cur = cur.next;
        }
        return dummy;
    }


第二种方法  通过归并排序来解决,

    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        // step 1. 通过两个指针不同移动速度行走将链表切分为等长的两段
        ListNode prev = null, slow = head, fast = head;

        while (fast != null && fast.next != null) {
            prev = slow;
            slow = slow.next;
            fast = fast.next.next;
        }

        prev.next = null;

        // step 2. 对两段链表进行排序
        ListNode l1 = sortList(head);
        ListNode l2 = sortList(slow);

        // step 3. 将两段链表进行合并
        return merge(l1, l2);
    }
		// 合并的方法,和数组合并的原理相同
    ListNode merge(ListNode l1, ListNode l2) {
        ListNode l = new ListNode(0), p = l;

        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                p.next = l1;
                l1 = l1.next;
            } else {
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }

        if (l1 != null)
            p.next = l1;

        if (l2 != null)
            p.next = l2;

        return l.next;
    }
//

第三题(lleetcode 22. Generate Parentheses,括号匹配问题)

leetcode 22. Generate Parentheses,括号匹配

题目描述
22. Generate Parentheses
Medium

Share
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]


// 利用回溯的方法可以减少匹配的复杂度
   /* public static List<String> generateParenthesis1(int n) {
        List<String> list = new ArrayList<String>();
        backtrack(list, "", 0, 0, n);
        return list;
    }

    public static void backtrack(List<String> list, String str, int open, int close, int max){
        // 递归的终止条件为字符串str的长度达到最大值
        if(str.length() == max*2){
            list.add(str);
            return;
        }
        // 先进行左括号递归
        if(open < max) {
            backtrack(list, str + "(", open + 1, close, max);
        }
        // 要达到匹配的条件为右括号的个数一定小于等于左括号,但这里得是"<",因为
        // 这一步 open 的值为max +1
        if(close < open) {
            backtrack(list, str + ")", open, close + 1, max);
        }
    }*/

以下题目的类型为动态规划、回溯、深度遍历

第四题(200. Number of Islands,区块计算问题)

200. Number of Islands

// 该题也是笔试常见题型,求1组成的区块岛屿有多少个,一般的思路是遍历一遍二维数组,在遍历的过程中,将属于同一区块的1设置为0,然后 count++,统计所有的区块个数
题目描述:
200. Number of Islands
Medium
Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example 1:

Input:
11110
11010
11000
00000

Output: 1


Example 2:

Input:
11000
11000
00100
00011

Output: 3




int n,m ;
    public int numIslands(char[][] grid) {
        int cout = 0;
        n = grid.length;
        m = grid[0].length;
        // 遍历数组,找到 字符为 '1' 的元素 并对所有和'1'字符相连的元素进行递归修改
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == '1') {
                    resulve(grid,i,j);
                    cout++;
                }
            }
        }
        return cout;
    }

    private void resulve(char[][] grid, int i, int j) {
        
        if (i < 0 || i >= n || j < 0 || j >= m || grid[i][j] =='0') {
            return;
        }
        // 将 '1' 字符修改为 '0',并对其上下左右的字符元素进行递归判断
        grid[i][j] = '0';
        resulve(grid,i+1,j);
        resulve(grid,i,j+1);
        resulve(grid,i,j-1);
        resulve(grid,i-1,j);
    }

第五题(91. Decode Ways,编码组合问题)

leetcod91. Decode Ways,编码组合问题

这类编码问题需要找到题目内在的规律,才能通过动态规划的转化表达式来求出结果,



题目描述:A message containing letters from A-Z is being encoded to numbers using the following mapping:

'A' -> 1
'B' -> 2
...
'Z' -> 26
Given a non-empty string containing only digits, determine the total number of ways to decode it.

Example 1:

Input: "12"
Output: 2
Explanation: It could be decoded as "AB" (1 2) or "L" (12).
Example 2:

Input: "226"
Output: 3
Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

题解代码如下:

class Solution {
    public int numDecodings(String s) {
          if(s == null || s.length()==0){
            return 0;
        }
 
        int[] dp = new int[s.length()];
        // 考虑字符为'0'的情况,字符为零的时候是不能被表示的
        dp[0] = s.charAt(0)=='0'? 0 : 1;
        for(int i=1; i<s.length();i++){
        // 分别将当前字符和此字符前一个字符的组合转成数字
            int cur = s.charAt(i)-'0';
            int pre = (s.charAt(i-1)-'0')*10+cur;
            //如果当前字符不等于0,则当前字符的组合种类dp[i]至少为dp[i-1]
            if(cur!=0){
                dp[i] = dp[i-1];
            }
            // 当两个数组合大于26的时候也是不能被编码的
            if(pre>=10 && pre<=26){
                dp[i] += i>=2? dp[i-2] : 1;
            } 
        }
        return dp[s.length()-1];
    }
}

第六题(91. Decode Ways,编码组合问题)

leetcod91. Decode Ways,编码组合问题





题目描述:139. Word Break


Favorite

Share
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

Note:

The same word in the dictionary may be reused multiple times in the segmentation.
You may assume the dictionary does not contain duplicate words.
Example 1:

Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".
Example 2:

Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
             Note that you are allowed to reuse a dictionary word.
Example 3:

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false```
这类题有多种解法,通用的解法是可以建立字典树,然后用字典树来找到每个单词的dp。当然也可以直接使用dp的方法,不过找转化方程要难度大很多。



纯使用dp的题解代码如下:

public class WordBreak139 {
    public boolean wordBreak(String s, List<String> wordDict) {

        int len = s.length();
        //用一个长度为length的boolean数组来表示每个单词是否可以匹配
        boolean[] v = new boolean[len+1];
        v[0] = true;

        for(int i = 0;i<len;i++){
            //如果前面有单词匹配成功,后面必然有一个v[i] = true
            if(!v[i]) {
                continue;
            }

            String substr = s.substring(i);
            // 遍历整个链表,找到可以匹配的单词
            for(String word:wordDict){
                if(substr.startsWith(word)){
                    v[ i + word.length()] = true;
                }
            }
        }

        return v[len];

    }
}


通用的剑字典树的解法
public class Solution {
    public class TrieNode {
        boolean isWord;
        TrieNode[] c;
        
        public TrieNode(){
            isWord = false;
            c = new TrieNode[128];
        }
    }
    
    public void addWord(TrieNode t, String w) {
        for (int i = 0; i < w.length(); i++) {
            int j = (int)w.charAt(i); 
            if (t.c[j] == null) t.c[j] = new TrieNode();
            t = t.c[j];
        }
        t.isWord = true;
    }
    
    public boolean wordBreak(String s, Set<String> wordDict) {
        TrieNode t = new TrieNode(), cur;
        // 建立字典树
        for (String i : wordDict) addWord(t, i);
        char[] str = s.toCharArray();
        int len = str.length;
        boolean[] f = new boolean[len + 1];
        f[len] = true;
        // 通过字典树来进行判断
        for (int i = len - 1; i >= 0; i--) {
            //System.out.println(str[i]);
            cur = t;
            for (int j = i; cur != null && j < len ; j++) {
                cur = cur.c[(int)str[j]];
                if (cur != null && cur.isWord && f[j + 1]) {
                    f[i] = true;
                    break;
                }
            }
        }
        return f[0];
    }
}

第八题(79. Word Search,常规矩阵字符串匹配问题,深度遍历类题型)

79. Word Search





79. Word Search
Medium


Share
Given a 2D board and a word, find if the word exists in the grid.

The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.

Example:

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

Given word = "ABCCED", return true.
Given word = "SEE", return true.
Given word = "ABCB", return false.
这类矩阵的字符串匹配问题通用的解法就是进行深度遍历,注意一些递归的条件基本能很快做出来

代码如下: 

public class WordSearch79 {

    public static void main(String[] args) {
        char[] []board =
                {
                        {'A', 'B', 'C', 'E'},
                        {'S', 'F', 'C', 'S'},
        {'A', 'D', 'E', 'E'}
    };
        String word = "ABCCED";
        boolean exist = exist(board, word);
        System.out.println(exist);
    }
    public static boolean exist(char[][] board, String word) {
        int row = board.length;
        int col = board[0].length;
        char[] chars = word.toCharArray();
        boolean flag = false;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
               /* if (board[i][j] == chars[0]) {
                    flag = getResult(board, chars, i, j, 0);
                    break;
                }*/
               if (getResult(board,chars,i,j,0)) {
                   return true;
               }
            }

        }
        return flag;
    }

    private static boolean getResult(char[][] board, char[] chars, int i, int j,int count) {

        if (count == chars.length) {
            return true;
        }
        if ( i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] != chars[count]  ) {
            return false;
        }
        // 这里需要把遍历过的字符标记一下,避免重复遍历
        board[i][j] = '*';
       boolean result = getResult(board,chars,i+1,j,count+1) ||
        getResult(board,chars,i-1,j,count+1) ||

        getResult(board,chars,i,j+1,count+1) ||

        getResult(board,chars,i,j-1,count+1);
       // 把标记的字符还原
       board[i][j] = chars[count];
       return result;
    }
    }
### ArrayList 和 LinkedList 的空间复杂度 对于 `ArrayList` 而言,其内部实现基于可调整大小的数组结构。这意味着当向 `ArrayList` 添加更多元素时,如果当前容量不足以容纳新元素,则会动态增加底层数组的大小。这种机制通常涉及创建一个新的更大数组并将现有元素复制过去。因此,在最坏情况下,除了存储实际数据所需的内存外,还可能额外消耗大约一半的空间用于预留增长所需的位置[^2]。 相比之下,`LinkedList` 是由节点组成的双向链表结构,每个节点不仅保存着元素本身的数据,还包括前后两个指针来指向相邻节点。这使得每次插入或删除操作只需修改局部几个节点之间的连接关系即可完成,而不需要像 `ArrayList` 那样移动大量连续内存中的元素位置。然而,由于每个元素都需要额外分配两个对象引用(即前驱和后继),所以即使在相同数量级下,`LinkedList` 占用的实际内存量也会比 `ArrayList` 更多一些[^1]。 为了直观展示两者之间差异,下面给出一段简单的代码示例: ```java import java.util.ArrayList; import java.util.LinkedList; public class SpaceComplexityComparison { public static void main(String[] args) { int numElements = 1_000_000; // 测试一百万个整数 long startMemoryUsageForArrayList = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); ArrayList<Integer> arrayListInstance = new ArrayList<>(numElements); for (int i = 0; i < numElements; ++i){ arrayListInstance.add(i); } long endMemoryUsageForArrayList = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); System.out.println("Space used by ArrayList: " + (endMemoryUsageForArrayList - startMemoryUsageForArrayList)); long startMemoryUsageForLinkedList = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); LinkedList<Integer> linkedListInstance = new LinkedList<>(); for (int i = 0; i < numElements; ++i){ linkedListInstance.add(i); } long endMemoryUsageForLinkedList = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); System.out.println("Space used by LinkedList: " + (endMemoryUsageForLinkedList - startMemoryUsageForLinkedList)); } } ``` 这段程序通过测量实例化并填充指定数目元素之前后的堆栈使用量变化,从而估算出不同容器类型所占用的大致字节数。请注意,具体数值可能会因 JVM 实现细节以及运行环境的不同有所波动。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值