LeetCode:数组专题
数组专题
有关数组的一些 leetcode 题,在此做一些记录,不然没几天就忘光光了
- 二分查找
- 双指针
- 滑动窗口
- 前缀和/差分数组
二分查找
本文内容摘录自公众号labuladong中有关二分查找的文章
总结
总体框架
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
寻找一个数(基本的二分搜索)
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意1
while(left <= right) { // <= 注意2
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意3
else if (nums[mid] > target)
right = mid - 1; // 注意4
}
return -1;
}
寻找左侧边界的二分搜索
- 写法1:
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid; // 注意:找到了后没有返回,而是继续寻找
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
// 返回方式1:这样返回的意思就是返回了比target小的个数
return left;
// 返回方式2:
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
}
- 写法2:
int left_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
// 搜索区间为 [left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] == target) {
// 收缩右侧边界
right = mid - 1;
}
}
// 检查出界情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
寻找右侧边界的二分查找
- 写法1:
int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意:能找到右边界,没有立即返回,而是继续向右搜索
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
// 返回方式1:返回了小于等于target的个数
return left - 1; // 注意
// 返回方式2:
if (left == 0) return -1;
return nums[left-1] == target ? (left-1) : -1;
}
- 写法2:
int right_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 这里改成收缩左侧边界即可
left = mid + 1;
}
}
// 这里改为检查 right 越界的情况,见下图
if (right < 0 || nums[right] != target)
return -1;
return right;
}
逻辑统一
// 寻找一个数
int binary_search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
// 寻找左侧边界的二分查找
int left_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
// 寻找右侧边界的二分查找
int right_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定右侧边界
left = mid + 1;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] != target)
return -1;
return right;
}
题目
875. 爱吃香蕉的珂珂
public int minEatingSpeed(int[] piles, int H) {
// piles 数组的最大值
int max = getMax(piles);
// 若:for(int k=1; k < max; k++){...}会有案例超时
// 通过二分法优化:寻找最小速度,就是寻找左侧边界
int left = 1, right = max - 1;
while(left <= right){
// 这样计算mid是为了防止溢出
int mid = left + (right - left) / 2;
// 以 speed 是否能在 H 小时内吃完香蕉
if(canFinish(piles, mid, H)){
right = mid-1;
}else{
left = mid+1;
}
}
// 最终结果就是返回最大值
return left;
}
private int getMax(int[] piles){
int max = piles[0];
for(int i=1; i<piles.length; i++){
if(max < piles[i]){
max = piles[i];
}
}
return max;
}
private boolean canFinish(int[] piles, int speed, int H){
int time = 0;
for(int n: piles){
time += n/speed + (n%speed == 0 ? 0: 1);
if(time > H){
return false;
}
}
return time <= H;
}
1011. 在 D 天内送达包裹的能力
// 最小的装在量为能装上最大的货物 最大的装载量就是全部都能装上
int max = 0;
int total = 0;
for(int i=0; i<weights.length; i++){
total += weights[i];
if(max < weights[i]){
max = weights[i];
}
}
int left = max, right = total;
while(left <= right){
// 目前闲置能装的量
int mid = left + (right - left)/2;
// 判断是否能装完
if(isFinish(weights, mid, D)){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left;
}
private boolean isFinish(int[] weights, int load, int D){
int one = 0;
int cost = 1;
for(int i=0; i<weights.length; i++){
one += weights[i];
if(one > load){
one = weights[i];
cost += 1;
}
}
return cost <= D;
}
双指针
这部分内容主要参考的是公众号 labuladong,中的文章双指针技巧汇总
总结
快慢指针
判断链表中是否有环
// 快慢指针法
public boolean hasCycle(ListNode head) { ListNode fast = head;
ListNode slow = head; while(fast != null && fast.next!=null){
fast = fast.next.next; // 快指针走两步
slow = slow.next; // 慢指针走一步
if(fast == slow){
return true;
}
}
return false;
}
已知链表中含有环,返回这个环的起始位置
在链表专题已经提及过:142. 环形链表 II
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head; while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next; if(fast == slow){
// 说明此时有环存在,当其中一个节点回到头结点
fast = head;
// 继续一步一步走,直到相遇,此时就是入环的节点
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
return null;
}
寻找链表的中点
类似上面的思路,快指针走两步,慢指针走一步,当快指针指向链表末尾时,慢指针此时就指向链表的中间节点,需要注意的是链表节点的个数为奇数偶数的情况
public boolean hasCycle(ListNode head) { ListNode fast = head;
ListNode slow = head; while(fast != null && fast.next!=null){
fast = fast.next.next; // 快指针走两步
slow = slow.next; // 慢指针走一步
}
// 需要注意的是:
// 当链表节点个数为奇数时,就是中间节点
// 而当为偶数时,slow指向的是考后边的那个节点
return slow;
}
寻找链表的倒数第 k 个元素
让快指针先走 k 步,然后快慢指针开始同速前进。这样当快指针走到链表末尾 null 时,慢指针所在的位置就是倒数第 k 个链表节点
// 方法2:双指针,指针1先走k,然后指针1,2一起走,指针1走到末尾后,返回指针2,指针2的距离即为:size-k
public int kthToLast(ListNode head, int k) { if(head == null){
return 0;
} ListNode node1 = head;
ListNode node2 = head;
// 指针1先走k步
while(k-- != 0){
node1 = node1.next;
} while(node1 != null){
node2 = node2.next;
node1 = node1.next;
} return node2.val;
}
左右指针
两数之和
// 二分法来一波
public int[] twoSum(int[] numbers, int target) { int left = 0; int right = numbers.length - 1; while(left < right){
int sum = numbers[left] + numbers[right]; if(sum == target){
return new int[]{left+1, right+1};
}else if(sum < target){
left ++;
}else if(sum > target){
right --;
}
} return new int[]{-1, -1};
}
反转数组
void reverse(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
// swap(nums[left], nums[right])
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++; right--;
}
}
滑动窗口算法
直接见下面
题目
26. 删除排序数组中的重复项
// 快慢指针去重
public int removeDuplicates(int[] nums) {
if(nums.length <= 1){
return nums.length;
}
int slow = 0;
int fast = 1;
while(fast < nums.length){
if(nums[fast] != nums[slow]){
nums[++slow] = nums[fast];
}
fast++;
}
return slow+1;
}
83. 删除排序链表中的重复元素
public ListNode deleteDuplicates(ListNode head) {
if(head == null){
return head;
}
ListNode ans = new ListNode(-1);
ans.next = head;
// 双指针,一个走在前一个走在后
ListNode fast = head.next;
ListNode slow = head;
while(fast != null){
if(fast.val != slow.val){
slow.next = fast;
slow = slow.next;
}
fast = fast.next;
}
// 这里必须断开连接
slow.next = null;
return ans.next;
}
27. 移除元素
public int removeElement(int[] nums, int val) {
if(nums.length == 0){
return nums.length;
}
int slow = 0;
int fast = 0;
while(fast < nums.length){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow ++;
}
fast ++;
}
return slow;
}
283. 移动零
public void moveZeroes(int[] nums) {
if(nums.length == 0){
return;
}
int fast = 0;
int slow = 0;
while(fast < nums.length){
if(nums[fast] != 0){
nums[slow] = nums[fast];
slow++;
}
fast ++;
}
while(slow < nums.length){
nums[slow] = 0;
slow++;
}
}
滑动窗口
总结
滑动窗口算法:维护一个窗口,不断滑动,然后更新答案
框架
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for(char c: t.toCharArray()){
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
while (right < s.length()) {
// c 是将移入窗口的字符
char c = s.charAt(right);
// 右移窗口
right++;
// 进行窗口内数据的一系列更新 [mark:和下面一个mark处的代码基本一致]
...
/*** debug 输出的位置 ***/
System.out.println("window: [" + left + "," + right +")");
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s.charAt(left);
// 左移窗口
left++;
// 进行窗口内数据的一系列更新 [mark:和上面一个mark处的代码基本一致]
...
}
}
}
使用时只需要思考以下四个问题:
当移动
right
扩大窗口,即加入字符时,应该更新哪些数据?什么条件下,窗口应该暂停扩大,开始移动
left
缩小窗口?当移动
left
缩小窗口,即移出字符时,应该更新哪些数据?我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
题目
套用上述模版给出两个题目,好好体会
76. 最小覆盖子串
public String minWindow(String s, String t) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for(char c: t.toCharArray()){
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, len = s.length()+1;
while(right < s.length()){
// c 是将移入窗口的字符
char c = s.charAt(right);
// 右移窗口
right ++;
// 进行窗口内数据的一系列更新
if(need.containsKey(c)){
window.put(c, window.getOrDefault(c, 0) + 1);
if(window.get(c).equals(need.get(c))){
valid++;
}
}
// 判断左侧窗口是否要收缩
while(valid == need.size()){
// 在这里更新最小覆盖子串
if(right - left < len){
start = left;
len = right - left;
}
// d 是将移出窗口的字符
char d = s.charAt(left);
left ++;
// 进行窗口内数据的一系列更新
if(need.containsKey(d)){
if(window.get(d).equals(need.get(d))){
valid --;
}
window.put(d, window.get(d)-1);
}
}
}
return len == s.length()+1 ? "" : s.substring(start, start+len);
}
567. 字符串的排列
public boolean checkInclusion(String s1, String s2) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for(char c: s1.toCharArray()){
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
while (right < s2.length()) {
// c 是将移入窗口的字符
char c = s2.charAt(right);
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
if(need.containsKey(c)){
window.put(c, window.getOrDefault(c, 0) + 1);
if(window.get(c).equals(need.get(c))){
valid++;
}
}
// 是否有这个排列:长度相等时进可以判断了
while((right-left) >= s1.length()){
// 在这里判断是否找到了合法的子串
if(valid == need.size()){
return true;
}
// 不等则左移窗口
// d 是将移出窗口的字符
char d = s2.charAt(left);
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
if(need.containsKey(d)){
if(window.get(d).equals(need.get(d))){
valid --;
}
window.put(d, window.get(d) - 1);
}
}
}
return false;
}
438. 找到字符串中所有字母异位词
public List<Integer> findAnagrams(String s, String p) {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
for(char c: p.toCharArray()){
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0;
int valid = 0;
List<Integer> res = new LinkedList<>();
while (right < s.length()) {
// c 是将移入窗口的字符
char c = s.charAt(right);
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
if(need.containsKey(c)){
window.put(c, window.getOrDefault(c, 0) + 1);
if(window.get(c).equals(need.get(c))){
valid++;
}
}
// 判断左侧窗口是否要收缩
while ((right-left) >= p.length()) {
if(valid == need.size()){
res.add(left);
}
// d 是将移出窗口的字符
char d = s.charAt(left);
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
if(need.containsKey(d)){
if(window.get(d).equals(need.get(d))){
valid--;
}
window.put(d, window.get(d) - 1);
}
}
}
return res;
}
3. 无重复字符的最长子串
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0, right = 0;
int len = 0;
while (right < s.length()) {
// c 是将移入窗口的字符
char c = s.charAt(right);
// 进行窗口内数据的一系列更新
if(window.getOrDefault(c, 0) < 1){
// 没有重复的时候的情况:右移窗口
right++;
window.put(c, window.getOrDefault(c, 0)+1);
if(right - left > len){
len = right - left;
}
}else{
// 一旦出现重复的时候:开始移左窗口
char d = s.charAt(left);
left++;
window.put(d, window.get(d) - 1);
}
}
return len;
}
前缀和/差分数组
前缀和
前缀和数组:一个数组的前n个元素的和而组成的数组,主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。
// 例如有一个数组 nums:[1,1,1] 其前缀和为:[0,1,2,3]
int n = nums.length;
// 前缀和数组
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++)
preSum[i + 1] = preSum[i] + nums[i];
案例题目:560. 和为K的子数组
public int subarraySum(int[] nums, int k) {
int n = nums.length;
// 前缀和数组
int[] preSum = new int[n+1];
preSum[0] = 0;
for(int i=0; i<n; i++){
preSum[i+1] = preSum[i] + nums[i];
}
int ans = 0;
// 穷举所有情况
for(int i=0; i<n; i++){
for(int j=i; j<n; j++){
if(k == preSum[j+1]- preSum[i]){
ans++;
}
}
}
return ans;
}
// 优化:k == preSum[j+1]- preSum[i] ---> preSum[j+1] - k = preSum[i]
public int subarraySum(int[] nums, int k) {
int n = nums.length;
//map: 前缀和 -> 该前缀和出现的次数
Map<Integer, Integer> preSum = new HashMap<>();
// base case
preSum.put(0, 1);
int ans=0, sum_0_i = 0;
for(int i=0; i<n; i++){
// 每个元素对应一个“前缀和”
sum_0_i += nums[i];
// 根据当前“前缀和”,在 map 中寻找「与之相减 == k」的历史前缀和
// [0...i] - [0...j] = k --> [j...i]是满足的
int sum_0_j = sum_0_i - k;
if(preSum.containsKey(sum_0_j)){
ans += preSum.get(sum_0_j);
}
preSum.put(sum_0_i, preSum.getOrDefault(sum_0_i, 0) + 1);
}
return ans;
}
差分数组
差分数组:就是数组中一个元素和前一个元素的差
int[] diff = new int[nums.length];
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
// 根据差分数组返回到原数组
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
这样构造差分数组diff
,就可以快速进行区间增减的操作,如果你想对区间nums[i..j]
的元素全部加 3,那么只需要让diff[i] += 3
,然后再让diff[j+1] -= 3
即可:
可以把差分数组抽象成一个类
class Difference {
// 差分数组
private int[] diff;
public Difference(int[] nums) {
assert nums.length > 0;
diff = new int[nums.length];
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
}
/* 给闭区间 [i,j] 增加 val(可以是负数)*/
public void increment(int i, int j, int val) {
diff[i] += val;
if (j + 1 < diff.length) {
diff[j + 1] -= val;
}
}
public int[] result() {
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
}
案例题目:1109. 航班预订统计
// 暴力解题:cost 1513 ms
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] res = new int[n];
for(int i=0; i<bookings.length; i++){
int j = bookings[i][0];
int k = bookings[i][1];
for(int len=j-1; len <=k-1; len++){
res[len] += bookings[i][2];
}
}
return res;
}
// 差分数组解题:cost 3ms
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] res = new int[n];
// 差分数组
int[] diff = new int[n];
for(int[] booking: bookings){
int i = booking[0] - 1;
int j = booking[1] - 1;
int val = booking[2];
// 对区间 nums[i..j] 增加 val
diff[i] += val;
if (j + 1 < diff.length) {
diff[j + 1] -= val;
}
}
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
LeetCode:数组专题的更多相关文章
- LeetCode 字符串专题(一)
目录 LeetCode 字符串专题 <c++> \([5]\) Longest Palindromic Substring \([28]\) Implement strStr() [\(4 ...
- Leetcode数组题*3
目录 Leetcode数组题*3 66.加一 题目描述 思路分析 88.合并两个有序数组 题目描述 思路分析 167.两数之和Ⅱ-输入有序数组 题目描述 思路分析 Leetcode数组题*3 66.加 ...
- 3. 现代 javascript 数组专题 和 对象专题
数组专题 展开运算符 使用...符号, 可以将数组"展开". 数组展开的妙用 ... eg: // 代替apply const foo = [1, 2, 3] const bar ...
- LeetCode树专题
LeetCode树专题 98. 验证二叉搜索树 二叉搜索树,每个结点的值都有一个范围 /** * Definition for a binary tree node. * struct TreeNod ...
- LeetCode 数组分割
LeetCode 数组分割 LeetCode 数组怎么分割可以得到左右最大值的差值的最大 https://www.nowcoder.com/study/live/489/1/1 左右最值最大差 htt ...
- LeetCode数组中重复的数字
LeetCode 数组中重复的数字 题目描述 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次. ...
- LeetCode数组解题模板
一.模板以及题目分类 1.头尾指针向中间逼近 ; ; while (pos1<pos2) { //判断条件 //pos更改条件 if (nums[pos1]<nums[pos2]) pos ...
- LeetCode数组查找问题
简单的二分查找法:(Leetcode704): 注意条件(low<=high) public int search(int[] nums, int target) { int low=0; in ...
- leetcode 数组类型题总结
1,removeDuplicates(I) int removeDuplicatesI(vector<int>& nums){ // 重新组织数组,同 removeDuplicat ...
随机推荐
- 第05课:GDB 常用命令详解(上)
本课的核心内容如下: run 命令 continue 命令 break 命令 backtrace 与 frame 命令 info break.enable.disable 和 delete 命令 li ...
- docker学习笔记(二)--配置镜像加速器
前提:docker已经安装好 配置过程 进入至阿里云开发中心,https://dev.aliyun.com/,点击管理中心 管理中心中,点击左侧镜像加速器. 修改配置文件,使用加速器,根据我们目前Do ...
- 关于在.H文件中定义变量
KEIL中,在".H"文件定义变量. 如果该".H"文件同时被两个".C"文件调用,则会出现重复定义错误(*** ERROR L104: M ...
- swiper-wrapper轮滑组件(多组轮滑界面)间隔无效问题
在多组此种轮滑效果出现时,你需要加两个属性值,即 new Swiper('.swiper-container', { slidesPerView: 3, slidesPerColumn: 2, spa ...
- 最新seo优化技巧
国内的SEO也发展不少年份了.我是最早开始从事SEO的那一班人.看着这个行业从零开始发展,长大.成熟还谈不上.可以这样说吧,国内做这个行业的,高手并不多.实战的高手更是寥寥无几.当然这个是我个人的推断 ...
- prometheus、node_exporter设置开机自启动
方法一.写入rc.local 在/etc/rc.local文件中编辑需要执行的脚本或者命令,我个人习惯用这个,因人而异,有的项目可能需要热加载配置文件,用服务会更好 #普罗米修斯启动,需要后面接con ...
- 华为云计算IE面试笔记-FusionCompute虚拟机热迁移定义,应用场景,迁移要求,迁移过程
*热迁移传送了什么数据?保存在哪? 虚拟机的内存.虚拟机描述信息(配置和设备信息).虚拟机的状态 虚拟机的配置和设备信息:操作系统(类别.版本号).引导方式(VM通过硬盘.光盘.U盘.网络启动)和引导 ...
- 一文让你彻底理解having和where的区别
having子句与where都是设定条件筛选的语句,有相似之处也有区别. having与where的区别: having是在分组后对数据进行过滤 where是在分组前对数据进行过滤 having后面可 ...
- hadoop生态之面试题篇
一.hdfs的高可用 1.先说下自己的理解, 正常的hdfs有namenode,datanode,secondnamenode,但是second name node 不是真正意义上的namenode备 ...
- Win10删除电脑3D对象等7个文件夹
把下面几个注册表项依次删除掉 "图片"文件夹:[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Expl ...