LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

381. O(1) 时间插入、删除和获取随机元素 - 允许重复

设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构。

注意: 允许出现重复元素。

insert(val):向集合中插入元素 val。 remove(val):当 val 存在时,从集合中移除一个 val。 getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。 示例:

// 初始化一个空的集合。
RandomizedCollection collection = new RandomizedCollection();

// 向集合中插入 1 。返回 true 表示集合不包含 1 。
collection.insert(1);

// 向集合中插入另一个 1 。返回 false 表示集合包含 1 。集合现在包含 [1,1] 。
collection.insert(1);

// 向集合中插入 2 ,返回 true 。集合现在包含 [1,1,2] 。
collection.insert(2);

// getRandom 应当有 2/3 的概率返回 1 ,1/3 的概率返回 2 。
collection.getRandom();

// 从集合中删除 1 ,返回 true 。集合现在包含 [1,2] 。
collection.remove(1);

// getRandom 应有相同概率返回 1 和 2 。
collection.getRandom();
//维护一个list存储所有val的值
//维护一个map存储每个val在List的位置
//随机只需要再list随机
//删除操作是将所要删除的数将在list中最后一个位置的数放到所要删除的位置
//在更新一下最有一个数在map中的位置即可
class RandomizedCollection {

    private Map<Integer,Set<Integer>> map  ;

    private List<Integer> list ;

    private Random random  ;

    private int size = 0 ;


    public RandomizedCollection() {
        map = new HashMap<>() ;
        list = new ArrayList<>() ;
        random = new Random() ;
    }

    public boolean insert(int val) {
        if(map.containsKey(val)){
            Set<Integer> indexes = map.get(val) ;
            list.add(size,val) ;
            indexes.add(size) ;
            size++ ;
            return false ;
        }else{
            Set<Integer> indexes = new HashSet<>() ;
            map.put(val,indexes) ;
            list.add(size,val) ;
            indexes.add(size) ;
            size++ ;
            return true ;
        }
    }

    public boolean remove(int val) {
        if(!map.containsKey(val)){
            return false ;
        }
        Set<Integer> indexes = map.get(val) ;
        if(list.get(size-1) == val){
            indexes.remove(size-1) ;
            size-- ;
        }else{
            //删除下标
            Iterator<Integer> it = indexes.iterator() ;
            int index = it.next() ;
            it.remove();
            //把list的最后一个值放到对应下标那
            int last = list.get(size-1) ;
            list.set(index,last) ;
            Set<Integer> set = map.get(last) ;
            //更改我最后一个值的下标
            set.remove(size-1) ;
            set.add(index) ;
            size-- ;
        }
        if(indexes.size() == 0){
            map.remove(val) ;
        }
        return true ;
    }

    public int getRandom() {
        return list.get(random.nextInt(size));
    }
}



/**
 * Your RandomizedCollection object will be instantiated and called as such:
 * RandomizedCollection obj = new RandomizedCollection();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */

382. 链表随机节点

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。

进阶: 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?

示例:

// 初始化一个单链表 [1,2,3]. ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); Solution solution = new Solution(head);

// getRandom()方法应随机返回1,2,3中的一个,保证每个元素被返回的概率相等。 solution.getRandom();

生成随机数,
如果随机数为0,就记录当前结点的值,一直循环,每次随机数为0,就记录当前结点的值
一直到链表循环结束
就可以做到随机访问的功能
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {

   private ListNode head;
    public Solution(ListNode head) {
       this.head = head;
    }
 
    public int getRandom() {
        int res = head.val;
        ListNode no = head.next;
        int i = 2;
        Random random = new Random();
        while(no!=null){
            if(random.nextInt(i) == 0){
                res = no.val;
            }
            i++;
            no = no.next;
        }
        return res;

    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(head);
 * int param_1 = obj.getRandom();
 */

383. 赎金信

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成。如果可以构成,返回 true ;否则返回 false。

(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。)

注意:

你可以假设两个字符串均只含有小写字母。

canConstruct("a", "b") -> false canConstruct("aa", "ab") -> false canConstruct("aa", "aab") -> true


String.indexof(a,b)
从b索引开始找a字符

class Solution {
   public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        int[] chars = new int[26];
        for (int i = 0; i < ransomNote.length(); i++) {
            char c = ransomNote.charAt(i);
            int index = magazine.indexOf(c, chars[c - 'a']);
            if (index == -1) {
                return false;
            }
            chars[c - 'a'] = index + 1;
        }
        return true;
    }
}

384. 打乱数组

打乱一个没有重复元素的数组。

示例:

// 以数字集合 1, 2 和 3 初始化数组。 int[] nums = {1,2,3}; Solution solution = new Solution(nums);

// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。 solution.shuffle();

// 重设数组到它的初始状态[1,2,3]。 solution.reset();

// 随机返回数组[1,2,3]打乱后的结果。 solution.shuffle();

class Solution {

     private int[] nums;
    private int[] originalNums;

    public Solution(int[] nums) {
        this.nums = nums;
        this.originalNums = Arrays.copyOf(nums, nums.length);
    }

    /**
     * Resets the array to its original configuration and return it.
     */
    public int[] reset() {
        return this.originalNums;
    }

    /**
     * Returns a random shuffling of the array.
     */
    public int[] shuffle() {
        Random random = new Random();
        for (int i = 0; i < nums.length / 2; i++) {
            // 每次只需拿第一个元素进行交换即可
            swap(nums, 0, random.nextInt(nums.length));
        }
        return nums;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(nums);
 * int[] param_1 = obj.reset();
 * int[] param_2 = obj.shuffle();
 */

385. 迷你语法分析器

给定一个用字符串表示的整数的嵌套列表,实现一个解析它的语法分析器。

列表中的每个元素只可能是整数或整数嵌套列表

提示:你可以假定这些字符串都是格式良好的:

字符串非空 字符串不包含空格 字符串只包含数字0-9, [, - ,, ]

示例 1:

给定 s = "324",

你应该返回一个 NestedInteger 对象,其中只包含整数值 324。

示例 2:

给定 s = "[123,[456,[789]]]",

返回一个 NestedInteger 对象包含一个有两个元素的嵌套列表:

1. 一个 integer 包含值 123
2. 一个包含两个元素的嵌套列表:
    i.  一个 integer 包含值 456
    ii. 一个包含一个元素的嵌套列表
         a. 一个 integer 包含值 789
扫描数字,
如果不是数字就扫描层数
扫描完层数,就递归调用方法,
只有最里层的时候才能直接打印
/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * public interface NestedInteger {
 *     // Constructor initializes an empty nested list.
 *     public NestedInteger();
 *
 *     // Constructor initializes a single integer.
 *     public NestedInteger(int value);
 *
 *     // @return true if this NestedInteger holds a single integer, rather than a nested list.
 *     public boolean isInteger();
 *
 *     // @return the single integer that this NestedInteger holds, if it holds a single integer
 *     // Return null if this NestedInteger holds a nested list
 *     public Integer getInteger();
 *
 *     // Set this NestedInteger to hold a single integer.
 *     public void setInteger(int value);
 *
 *     // Set this NestedInteger to hold a nested list and adds a nested integer to it.
 *     public void add(NestedInteger ni);
 *
 *     // @return the nested list that this NestedInteger holds, if it holds a nested list
 *     // Return null if this NestedInteger holds a single integer
 *     public List<NestedInteger> getList();
 * }
 */
class Solution {
      public NestedInteger deserialize(String s) {
        if(s.charAt(0)!='[') {
            return new NestedInteger(Integer.valueOf(s));
        }
        else {
            return deserialize1(s.substring(1));
        }
    }

    public NestedInteger deserialize1(String s) {
        NestedInteger res = new NestedInteger();
        //从左到右扫描
        for(int i=0;i<s.length();i++) {
            char c = s.charAt(i);
            if(c>='0'&&c<='9'||c=='-') {
                int n = 0; int flag = 1;
                for(;i<s.length();i++) {
                    c = s.charAt(i);
                    if(c>='0'&&c<='9') {
                        n = n*10 + c-'0';
                    } else if(c=='-'){
                        flag = -1;
                    } else {
                        i = i-1;
                        break;
                    }
                }
                res.add(new NestedInteger(flag*n));
            }
            else if(c=='[') {
                int index = i;
                int counter = 0;
                for(;i<s.length();i++) {
                    c = s.charAt(i);
                    if(c=='[') counter++;
                    else if(c==']') counter--;
                    if(counter==0) {
                        res.add(deserialize1(s.substring(index+1,i)));
                        break;
                    }
                }
            }
        }
        return res;
    }
}

386. 字典序排数

给定一个整数 n, 返回从 1 到 n 的字典顺序。

例如,

给定 n =1 3,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] 。

请尽可能的优化算法的时间复杂度和空间复杂度。 输入的数据 n 小于等于 5,000,000。

通过次数6,670提交次数9,748

字典序,每次都是从1到9开始排,只有到最后一位的时候递归调用,会直接返回
然后每次都是递归调用下一位,更改下一位的
class Solution {
   void reversalTree2(int root, int n,  ArrayList<Integer> list) {
	    list.add(root);
	    	  
	    	  
	    int start = root * 10;
	    for (int i = start; i <= n && i <= start + 9; i++) {
	    	 reversalTree2(i, n, list);
	    }
	    	  
	}
	      
	public List<Integer> lexicalOrder(int n) {
	    ArrayList<Integer> list = new ArrayList<>(n);
	    	  
	
	    for (int i=1; i <= n && i <= 9; i++) {
		    reversalTree2(i, n, list);
	    }
	    	  
	    return list;
	}

}

387. 字符串中的第一个唯一字符

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

案例:

s = "leetcode" 返回 0.

s = "loveleetcode", 返回 2.

注意事项:您可以假定该字符串只包含小写字母。

indexOf找到第一个对应字符的下标
lastIndexOf找到最后一个对应字符的下标
如果都存在,并且相等下标,说明只有一个该字符,
就记录下,匹配是否是最小的,保存最小的
如果n还是等于字符串长度,证明都是重复的字符,返回-1
class Solution {
 public int firstUniqChar(String s) {
        //fast
        int n = s.length();
        
        for(int i = 'a'; i<='z';i++){
            int start = s.indexOf(i);
            int end = s.lastIndexOf(i);
            
            
            if(start == end && start != -1){
                n = Math.min(start, n);
            }
        }
        
        if(n==s.length()){
            return -1;
        }else{
            return n;
        }
}}

388. 文件的最长绝对路径

假设我们以下述方式将我们的文件系统抽象成一个字符串:

字符串 "dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext" 表示:

dir
    subdir1
    subdir2
        file.ext

目录 dir 包含一个空的子目录 subdir1 和一个包含一个文件 file.ext 的子目录 subdir2 。

字符串 "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext" 表示:

dir
    subdir1
        file1.ext
        subsubdir1
    subdir2
        subsubdir2
            file2.ext

目录 dir 包含两个子目录 subdir1 和 subdir2。 subdir1 包含一个文件 file1.ext 和一个空的二级子目录 subsubdir1。subdir2 包含一个二级子目录 subsubdir2 ,其中包含一个文件 file2.ext。

我们致力于寻找我们文件系统中文件的最长 (按字符的数量统计) 绝对路径。例如,在上述的第二个例子中,最长路径为 "dir/subdir2/subsubdir2/file2.ext",其长度为 32 (不包含双引号)。

给定一个以上述格式表示文件系统的字符串,返回文件系统中文件的最长绝对路径的长度。 如果系统中没有文件,返回 0。

说明:

文件名至少存在一个 . 和一个扩展名。 目录或者子目录的名字不能包含 .。 要求时间复杂度为 O(n) ,其中 n 是输入字符串的大小。

请注意,如果存在路径 aaaaaaaaaaaaaaaaaaaaa/sth.png 的话,那么 a/aa/aaa/file1.txt 就不是一个最长的路径。

	“\t是一个字符”
        其他的直接暴力搜索就行
        合理利用正则表达式可以提高效率
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Solution {
  //bx
     public int lengthLongestPath(String input) {
        if (input.length() == 0) {
            return 0;
        }
        int res = 0;
        String[] dirs = input.split("\n");
        int[] sum = new int[dirs.length+1];
        //StringBuilder sb = new StringBuilder();

        for (String s : dirs) {
            int level = s.lastIndexOf('\t') + 2;
            //if (level == 2){
                //sb.setLength(sum[1]);
            //}
            //sb.append(s.substring(s.lastIndexOf("\t")+1));
            int len = s.length() - (level - 1);
            if (s.contains(".")) {
                res = Math.max(res, sum[level - 1] + len);
            } else {
                sum[level] = sum[level - 1] + len + 1;  //是目录,要+1,目录有个/的
                //sb.append("\\");
            }

        }
        //System.out.println(sb.toString());
        return res;
    }
}

389. 找不同

给定两个字符串 s 和 t,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

示例:

输入: s = "abcd" t = "abcde"

输出: e

解释: 'e' 是那个被添加的字母。

class Solution {
    //     public char findTheDifference(String s, String t) {
    //     char res = t.charAt(t.length()-1);
    //     for(int i=0; i<s.length(); i++){
    //         res ^= s.charAt(i);
    //         res ^= t.charAt(i);
    //     }
    //     return res;
    // }
     public char findTheDifference(String s, String t) {
        char[] ss = s.toCharArray();
        char[] tt = t.toCharArray();
        char res = tt[tt.length - 1];
        for(int i=0; i<ss.length; i++){
            res += tt[i] - ss[i];
        }
        return res;
    }
}

390. 消除游戏

给定一个从1 到 n 排序的整数列表。 首先,从左到右,从第一个数字开始,每隔一个数字进行删除,直到列表的末尾。 第二步,在剩下的数字中,从右到左,从倒数第一个数字开始,每隔一个数字进行删除,直到列表开头。 我们不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。 返回长度为 n 的列表中,最后剩下的数字。

示例:

输入: n = 9, 1 2 3 4 5 6 7 8 9 2 4 6 8 2 6 6

输出: 6

isNormal是判断是单数行还是双数行
单数行就每隔一个数字找一个
双数也是每隔一个找一个,但是是后面找的,其实后面找和正面找一样的,最后找的是最后一个数字
        倒着找的时候,如果是2的倍数,就从开头开始,如果不是2的倍数就默认走一步
        因为倒着走过来,不是二的倍数,走不到最开始的地方
每次都数组长度除2
步数乘2(相当于上面已经删掉了一部分)



	最下面那一行是真正大佬的思路
        
class Solution {
    public int lastRemaining(int n) {
 boolean isNormal = true;
    int len = n;
    int start = 1;
    int step = 1;
    while (len > 1) {
        if (isNormal) {
            start = start + step;
        } else {
            start = len % 2 == 0 ? start : start + step;
        }
        step = step * 2;
        len = len / 2;
        isNormal = !isNormal;
    }
    return start;
    //  return n == 1 ? 1 : 2 * (n / 2 + 1 - lastRemaining(n / 2));
    }
}