398. 随机数索引【中等题】【每日一题】
思路:
方法一:【哈希表】
用哈希表存储每个数及其对应的索引,然后在
target
对应的索引里边随机返回一个索引作为答案。
代码:
class Solution {
Map<Integer, List<Integer>> map;
Random random = new Random();
public Solution(int[] nums) {
map = new HashMap<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
map.putIfAbsent(nums[i], new ArrayList<>());
map.get(nums[i]).add(i);
}
}
public int pick(int target) {
//随机输出索引
List<Integer> indexs = map.get(target);
return indexs.get(random.nextInt(indexs.size()));
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(nums);
* int param_1 = obj.pick(target);
*/
方法二:【蓄水池抽样】
依次遍历所有的数字,如果遇到
target
就将计数变量cnt
加1
,这样可以统计所有的target
出现的次数,我们只要将这cnt
个target
保持概率均等抽取即可,即我们要返回的答案索引index
被选中的概率应为1/cnt
。
那么我们只需保持每次抽到
target
时,当选中概率为当前的1/cnt
时,将答案索引index
更新为当前索引即可。
如何确保选中概率为当前的
1/cnt
,只需在[0,cnt)
范围内随机抽取到的数是0
即可。(当然也不一定非得是0
,只要在那个范围内就行,反正概率都一样,取0
是为了表述方便,也可以取尾端cnt-1
,不影响最后的求解)
代码:
class Solution {
Random random = new Random();
int[] nums;
public Solution(int[] nums) {
this.nums = nums;
}
public int pick(int target) {
int n = nums.length,index = 0,cnt = 0;
for(int i = 0; i < n ; i++){
if(nums[i] == target){
cnt++;
if(random.nextInt(cnt) == 0){
index = i;
}
}
}
return index;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(nums);
* int param_1 = obj.pick(target);
*/
382. 链表随机节点【中等题】
思路:【蓄水池抽样】
与上题一个套路,具体实现不做讲解,写本题是为了巩固知识。
另外提交的过程中必须将head
再拷贝一遍,要不然无法返回正确结果,本人较菜懒得研究是为什么了,对比题解之后我将head
再拷贝一遍(ListNode t = head;
)就可以返回正确结果了。
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
Random random = new Random();
ListNode head;
public Solution(ListNode head) {
this.head = head;
}
public int getRandom() {
int ans = 0,cnt = 0;
ListNode t = head;
while(t != null){
cnt++;
if(random.nextInt(cnt) == 0){
ans = t.val;
}
t = t.next;
}
return ans;
}
}
/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(head);
* int param_1 = obj.getRandom();
*/
剑指 Offer II 016. 不含重复字符的最长子字符串【中等题】
思路:【滑动窗口】
先固定左边界,再固定右边界,然后每次左边界右移
1
位,对应更新右边界,求出每一个符合题意的子串,并将其长度与max
比较,更新max
,最后返回max
即可。
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
if (n <= 1){//长度为0,返回0,长度为1,返回1
return n;
}
// 哈希集合,记录每个字符是否出现过
Set<Character> set = new HashSet<>();
//窗口右边界,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int right = -1, max = 0;
for (int left = 0; left < n; left++) {//每次遍历更新窗口左边界
if (left != 0) {
//窗口左边界右移1位,对应set中删除原左边界字符
set.remove(s.charAt(left - 1));
}
while (right + 1 < n && !set.contains(s.charAt(right + 1))) {
// 不断地扩大右边界,并将遇到的字符加入set集合,直到遇见重复的为止,此时right位置字符与前边字符重复,退出while循环
set.add(s.charAt(right + 1));
right++;
}
// [left,right)区间窗口是一个极长的无重复字符子串,其长度为right-left+1
max = Math.max(max, right - left + 1);//更新max
}
return max;
}
}