问题描述:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
//思路一:桶排序
//设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。
//把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
//统计数组中元素出现频率
HashMap<Integer,Integer> map = new HashMap<>();
for(int num : nums){
map.put(num, map.getOrDefault(num,0)+1);
}
//每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率
List<Integer>[] buckets = new ArrayList[nums.length+1];
for(Integer num : map.keySet()){
Integer freq = map.get(num);
if(buckets[freq] == null){
buckets[freq] = new ArrayList<>();
}
buckets[freq].add(num);
}
List<Integer> res = new ArrayList<>();
for(int i=buckets.length-1;i>=0 && res.size()<k;i--){
if(buckets[i]==null){
continue;
}
if(buckets[i].size() <= (k-res.size())){
res.addAll(buckets[i]);
}else{
res.addAll(buckets[i].subList(0,k-res.size()));
}
}
return res;
}
}//思路二:堆排序
//利用堆排序
class Solution{
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer> res = new ArrayList<>();
HashMap<Integer,Integer> map = new HashMap<>();
for(int num : nums){
int freq = map.getOrDefault(num,0);
map.put(num,++freq);
}
Set<Integer> set = map.keySet();
Element[] elements = new Element[set.size()];
int i=0;
for(Integer key : set){
elements[i] = new Element(key,map.get(key));
i++;
}
PriorityQueue<Element> pq = new PriorityQueue<>();
for(Element element : elements){
pq.add(element);
if(pq.size() > k){
pq.poll();
}
}
while(!pq.isEmpty()){
Element element = pq.poll();
res.add(element.num);
}
return res;
}
}
class Element implements Comparable<Element>{
int num;
int freq;
Element(int num,int freq){
this.num = num;
this.freq = freq;
}
@Override
public int compareTo(Element o) {
return this.freq - o.freq;
}
}问题描述:给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
示例 1:
输入:
"tree"
输出:
"eert"
解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入:
"cccaaa"
输出:
"cccaaa"
解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入:
"Aabb"
输出:
"bbAa"
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
class Solution {
public String frequencySort(String s) {
//统计 s 中字符串出现的次数
HashMap<Character,Integer> map = new HashMap<>();
for(int i=0;i<s.length();i++){
char c = s.charAt(i);
map.put(c,map.getOrDefault(c,0)+1);
}
List<Character>[] buckets = new ArrayList[s.length()+1];
for(Character c : map.keySet()){
Integer freq = map.get(c);
if(buckets[freq]==null){
buckets[freq] = new ArrayList<>();
}
buckets[freq].add(c);
}
StringBuilder res = new StringBuilder();
for(int freq = buckets.length-1;freq>=0;freq--){
if(buckets[freq] == null){
continue;
}
List<Character> list = buckets[freq]; //list 中所有元素都出现了 freq 次数
for(Character c : list){
for(int i=0;i<freq;i++){
res.append(c);
}
}
}
return res.toString();
}
}
问题描述:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意: 不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:
- 一个直观的解决方案是使用计数排序的两趟扫描算法。 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
//思路如上图:
//i指向值为0的元素,swap(i,zero+1) ,交换后,i位置元素是1(nums[zero+1..i+1]==1),
//不需要处理,i直接+1
//i指向值为1的元素,不需要处理直接+1
//i指向值为2的元素,swap(i,two-1),交换后,two-1位置元素就是2
public void sortColors(int[] nums) {
int zero = -1;
int two = nums.length;
int i = 0;
while(i<two){
if(nums[i] ==0){
zero++;
swap(nums,zero,i);
i++;
}else if(nums[i] ==2){
two--;
swap(nums,two,i);
}else{
i++;
}
}
}
private void swap(int[] nums,int i,int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
问题描述:在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
//思路:
//1、注意:这里求的是求第k个最大元素,就是求(nums.length-k+1)个最小元素
//2、求第m小的元素,是非常典型的快速排序的应用
public int findKthLargest(int[] nums, int k) {
//1 <= k <= nums.length
k = nums.length - k + 1; //转化为第k小元素问题
int lo = 0;
int hi = nums.length -1;
while(lo<=hi){
int p = partition(nums,lo,hi);
if(p == k-1){
return nums[p];
}else if(p<(k-1)){
lo++;
}else{ //p> k-1
assert p > (k-1);
hi--;
}
}
return nums[0];
}
private int partition(int[] nums,int l,int r){
int pivot = nums[l];
while(l < r){
//从右向左查找第一个 < pivot 的元素
while(l < r && nums[r] >= pivot){
r--;
}
nums[l] = nums[r];
//从左向右查找第一个 > pivot 的元素
while(l < r && nums[l] <= pivot){
l++;
}
nums[r] = nums[l];
}
nums[l] = pivot;
return l;
}
给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。
示例 1:
输入: "2-1-1"
输出: [0, 2]
解释:
((2-1)-1) = 0
(2-(1-1)) = 2
示例 2:
输入: "2*3-4*5"
输出: [-34, -14, -10, -10, 10]
解释:
(2*(3-(4*5))) = -34
(2*((3-4)*5)) = -10
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(((2*3)-4)*5) = 10
public List<Integer> diffWaysToCompute(String input) {
List<Integer> res = new ArrayList<>();
for(int i=0;i<input.length();i++){
char c = input.charAt(i);
if( c =='+' || c=='-' || c=='*'){ //根据运算符进行分治
List<Integer> left = diffWaysToCompute(input.substring(0,i));
List<Integer> right = diffWaysToCompute(input.substring(i+1));
System.out.println(input);
System.out.println("left:"+left+",right:"+right);
System.out.println("==================");
for(int l : left){
for(int r : right){
switch (c){
case '+' :
res.add(l+r);
break;
case '-' :
res.add(l-r);
break;
case '*' :
res.add(l*r);
break;
}
}
}
}
}
if(res.size() ==0){ //说明input 是一个数字
res.add(Integer.valueOf(input));
}
return res;
}给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
//思路:分治法
//如果把数组分成左右两段,那么加和最大的连续子序列,
//要么出现在数组的左半部分,
//要么出现在数组的右半部分,
//要么出现在中间,即从左半部分和右半部分相邻的地方各取一段。
public int maxSubArray(int[] nums) {
return maxSubArray(nums,0,nums.length-1);
}
private int maxSubArray(int[] nums,int l,int r){
if(l==r){
return nums[l];
}
int mid = l + (r-l)/2;
int leftSubMax = maxSubArray(nums,l,mid);
int rightSubMax = maxSubArray(nums,mid+1,r);
int subMax = nums[mid];
int sum = subMax; //中间元素必然在该最大连续子序列中
//从中间向左半数组遍历
for(int i=mid-1;i>=l;i--){
sum += nums[i];
subMax = Math.max(subMax,sum);
}
sum = subMax;
//从中间向右半数组遍历
for(int i=mid+1;i<=r;i++){
sum += nums[i];
subMax = Math.max(subMax,sum);
}
return max3(leftSubMax,subMax,rightSubMax);
}
private int max3(int a,int b,int c){
int tmp = (a>b) ? a:b;
return (tmp>c? tmp : c);
}给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。
示例:
输入: 3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
public List<TreeNode> generateTrees(int n) {
if(n==0){
return new LinkedList<>();
}
return generateSubTrees(1,n);
}
public List<TreeNode> generateSubTrees(int s,int e){
List<TreeNode> res = new LinkedList<>();
if(s>e){
res.add(null);
return res;
}
for(int i=s;i<=e;i++){
List<TreeNode> left = generateSubTrees(s,i-1);
List<TreeNode> right = generateSubTrees(i+1,e);
for(TreeNode l : left){
for(TreeNode r : right){
TreeNode root = new TreeNode(i);
root.left = l;
root.right = r;
res.add(root);
}
}
}
return res;
}保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
Input: [1,2], [1,2,3]
Output: 2
Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
You have 3 cookies and their sizes are big enough to gratify all of the children,
You need to output 2.题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因为最小的孩子最容易得到满足,所以先满足最小的孩子。
证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,给该孩子分配第 n 个饼干,并且 m < n。我们可以发现,经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。
//思路:贪心策略
//每次选取最大的饼干给最"贪心"的小朋友。
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int gi = g.length-1;
int si = s.length-1;
int res=0;
while(gi>=0 && si>=0){
if(s[si]>=g[gi]){
res++;
gi--;
si--;
}else{ //不能满足这个小朋友,抱歉,饼干就不分给他了。
gi--;
}
}
return res;
}435. Non-overlapping Intervals (Medium)
Input: [ [1,2], [1,2], [1,2] ]
Output: 2
Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.Input: [ [1,2], [2,3] ]
Output: 0
Explanation: You don't need to remove any of the intervals since they're already non-overlapping.题目描述:计算让一组区间不重叠所需要移除的区间个数。
先计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。
public int eraseOverlapIntervals(Interval[] intervals) {
if (intervals.length == 0) {
return 0;
}
Arrays.sort(intervals, Comparator.comparingInt(o -> o.end));
int cnt = 1;
int end = intervals[0].end;
for (int i = 1; i < intervals.length; i++) {
if (intervals[i].start < end) {
continue;
}
end = intervals[i].end;
cnt++;
}
return intervals.length - cnt;
}使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:
Arrays.sort(intervals, new Comparator<Interval>() {
@Override
public int compare(Interval o1, Interval o2) {
return o1.end - o2.end;
}
});452. Minimum Number of Arrows to Burst Balloons (Medium)
Input:
[[10,16], [2,8], [1,6], [7,12]]
Output:
2
题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。
也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。
public int findMinArrowShots(int[][] points) {
if (points.length == 0) {
return 0;
}
Arrays.sort(points, Comparator.comparingInt(o -> o[1]));
int cnt = 1, end = points[0][1];
for (int i = 1; i < points.length; i++) {
if (points[i][0] <= end) {
continue;
}
cnt++;
end = points[i][1];
}
return cnt;
}406. Queue Reconstruction by Height(Medium)
Input:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
Output:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
为了使插入操作不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入的第 k 个位置可能会变成第 k+1 个位置。
身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
public int[][] reconstructQueue(int[][] people) {
if (people == null || people.length == 0 || people[0].length == 0) {
return new int[0][0];
}
Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));
List<int[]> queue = new ArrayList<>();
for (int[] p : people) {
queue.add(p[1], p);
}
return queue.toArray(new int[queue.size()][]);
}
763. Partition Labels (Medium)
Input: S = "ababcbacadefegdehijhklij"
Output: [9,7,8]
Explanation:
The partition is "ababcbaca", "defegde", "hijhklij".
This is a partition so that each letter appears in at most one part.
A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
public List<Integer> partitionLabels(String S) {
int[] lastIndexsOfChar = new int[26];
for (int i = 0; i < S.length(); i++) {
lastIndexsOfChar[char2Index(S.charAt(i))] = i;
}
List<Integer> partitions = new ArrayList<>();
int firstIndex = 0;
while (firstIndex < S.length()) {
int lastIndex = firstIndex;
for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) {
int index = lastIndexsOfChar[char2Index(S.charAt(i))];
if (index > lastIndex) {
lastIndex = index;
}
}
partitions.add(lastIndex - firstIndex + 1);
firstIndex = lastIndex + 1;
}
return partitions;
}
private int char2Index(char c) {
return c - 'a';
}
Input: flowerbed = [1,0,0,0,1], n = 1
Output: True
题目描述:花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int len = flowerbed.length;
int cnt = 0;
for (int i = 0; i < len && cnt < n; i++) {
if (flowerbed[i] == 1) {
continue;
}
int pre = i == 0 ? 0 : flowerbed[i - 1];
int next = i == len - 1 ? 0 : flowerbed[i + 1];
if (pre == 0 && next == 0) {
cnt++;
flowerbed[i] = 1;
}
}
return cnt >= n;
}
s = "abc", t = "ahbgdc"
Return true.
public boolean isSubsequence(String s, String t) {
int index = -1;
for (char c : s.toCharArray()) {
index = t.indexOf(c, index + 1);
if (index == -1) {
return false;
}
}
return true;
}
665. Non-decreasing Array (Easy)
Input: [4,2,3]
Output: True
Explanation: You could modify the first 4 to 1 to get a non-decreasing array.
题目描述:判断一个数组能不能只修改一个数就成为非递减数组。
在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 不影响后续的操作 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。
public boolean checkPossibility(int[] nums) {
int cnt = 0;
for (int i = 1; i < nums.length && cnt < 2; i++) {
if (nums[i] >= nums[i - 1]) {
continue;
}
cnt++;
if (i - 2 >= 0 && nums[i - 2] > nums[i]) {
nums[i] = nums[i - 1];
} else {
nums[i - 1] = nums[i];
}
}
return cnt <= 1;
}
122. Best Time to Buy and Sell Stock II (Easy)
题目描述:一次股票交易包含买入和卖出,多个交易之间不能交叉进行。
对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中,从而在局部最优的情况下也保证全局最优。
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
profit += (prices[i] - prices[i - 1]);
}
}
return profit;
}
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int preSum = nums[0];
int maxSum = preSum;
for (int i = 1; i < nums.length; i++) {
preSum = preSum > 0 ? preSum + nums[i] : nums[i];
maxSum = Math.max(maxSum, preSum);
}
return maxSum;
}
121. Best Time to Buy and Sell Stock (Easy)
题目描述:只进行一次交易。
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
public int maxProfit(int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int soFarMin = prices[0];
int max = 0;
for (int i = 1; i < n; i++) {
if (soFarMin > prices[i]) soFarMin = prices[i];
else max = Math.max(max, prices[i] - soFarMin);
}
return max;
}class LRUCache {
private List<Integer> list; //使用 LinkedList 来代替双向链表,维护数据顺序
private Map<Integer,Integer> map; //使用 map 保存数据,实现 O(1) 的访问
private int size;//链表长度
private int capacity;//缓存总量,方便后面判断是否链表长度是否超过缓存容量
public LRUCache(int capacity) {
list = new LinkedList<>();
map = new HashMap<>();
size=0;
this.capacity = capacity;
}
//判断 key 在 HashMap 中是否存在,
//如果存在,则把对应的节点移动到链表头部,并返回对应的 value 值;
//如果不存在,则返回-1。
public int get(int key) {
if(!map.containsKey(key)){
return -1;
}
//将对应节点移动到链表头部
list.remove((Integer)key);
list.add(0,key);
return map.get(key);
}
//如果 key 在 HashMap 中存在,则先重置对应的 value 值,将节点移动到链表的头部;
//如果 key 在 HashMap 中不存在,则新建一个节点,并将节点放到链表的头部。
//注意:当 Cache 存满的时候,将链表最后一个节点删除。
public void put(int key, int value) {
if(map.containsKey(key)){
map.put(key,value);
//将节点移到链表的头部
list.remove((Integer)key);
list.add(0,key);
}else{
list.add(0,key);
map.put(key,value);
size++;
if(size>capacity){ //每次插入新元素,都会检查是否超过 capacity,只会多出一个元素
//如果超过容量,则删除链表最后一个元素,同时要去相应的 map 中删除元素
int lastElement = list.remove(list.size()-1);
map.remove(lastElement);
}
}
}
}
@Test
public void test(){
LRUCache cache = new LRUCache( 2);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
System.out.println(cache.get(2)); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
System.out.println(cache.get(1)); // 返回 -1 (未找到)
System.out.println(cache.get(3)); // 返回 3
System.out.println(cache.get(4)); // 返回 4
}线段切割法:在一条线段上找 (N-1) 个随机点,就可以将该线段随机且公平地切割成 N 段。
//思路:线段切割法
//在一条线段上找 N-1 个随机点,就可以将该线段随机且公平地切割成 N 段。
public class RedPocket {
public List<Integer> generatePacketsByLineCutting(int people, int money) {
List<Integer> res = new ArrayList<>();
//在[0,money] 这条线段上找 (people-1) 个随机点
Set<Integer> points = new TreeSet<>(); //存储(people-1)个随机点
//使用 treeset 存储,保证这些点是有序的,方便后面的切分
Random random = new Random();
while(points.size() < people-1){
points.add(random.nextInt(money));
}
//此时 points.size() == people-1
points.add(money);
int pre=0;
for(int point:points){
res.add(point-pre);
pre = point;
}
return res;
}
@Test
public void test(){
int people =10;
int money = 100;
List<Integer> res= generatePacketsByLineCutting(people,money);
System.out.println(res);
int result =0;
for(Integer d:res){
result += d;
}
System.out.println("reslut:"+result);
}
}// 算法的基本思想:每次从一组数中随机选出一个数,
// 然后与最后一个数交换位置,并且不再考虑最后一个数。
// 时间复杂度:O(N)
public static void shuffle(int[] nums){
Random random = new Random();
for(int i=nums.length-1;i>=0;i--){
int j = random.nextInt(i+1); //在[0,i]范围内
if(i==j){ //一个小优化
continue;
}
swap(nums,i,j); // i 指向数组最后一个元素
}
}
private static void swap(int[] nums,int i,int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public static void main(String[] args) {
int[] nums = {1,2,3,4,5,6,7,8};
shuffle(nums);
for(int num:nums){
System.out.println(num);
}
}给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
private int m;
private int n;
//用来标记是否是同一个岛屿
private boolean[][] visited;
//TODO:二维数组四个方向查找的小技巧
private int[][] d={{-1,0},{0,1},{1,0},{0,-1}};
private boolean isArea(int x,int y){
return (x>=0 && x<m) && (y>=0 && y<n);
}
//找到一个岛,就将这个岛标记
private void dfs(char[][] grid, int x, int y) {
//[x,y]是陆地,并且标记为已经访问的了
visited[x][y]=true;
//向四个方向去扩散
for(int i=0;i<4;i++){
int newx=x+d[i][0];
int newy=y+d[i][1];
if(isArea(newx,newy)){
//[newx,newy]合法
if(grid[newx][newy]=='1' && visited[newx][newy]==false){
//[newx,newy]位置陆地,并且未被标记,进行深度优先遍历
//如果已经被标记了,则直接忽略
////找到一个岛,就将整个岛进行标记
dfs(grid,newx,newy);
}
}
}
}
public int numIslands(char[][] grid) {
m=grid.length;
if(m==0){
return 0;
}
n=grid[0].length;
visited=new boolean[m][n];
int res=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
//有陆地,并且这块陆地未被标记,则就是一个新的岛屿
if(grid[i][j]=='1' && visited[i][j]==false){
res++;
//使用floodfill算法来标记这个新岛屿岛屿
dfs(grid,i,j);
}
}
}
return res;
}
给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是11,因为岛屿只能包含水平或垂直的四个方向的‘1’。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。
注意: 给定的矩阵grid 的长度和宽度都不超过 50。
private int m;
private int n;
//用来标记是否是同一个岛屿
private boolean[][] visited;
//TODO:二维数组四个方向查找的小技巧
private int[][] d={{-1,0},{0,1},{1,0},{0,-1}};
private boolean isArea(int x,int y){
return (x>=0 && x<m) && (y>=0 && y<n);
}
//找到一个岛,就将这个岛标记
//返回的值就是[x,y]所在岛屿的面积
private int dfs(int[][] grid, int x, int y) {
if(grid[x][y] == 0){
return 0;
}
//[x,y]是陆地,并且标记为已经访问的了
visited[x][y]=true;
int area=1;
//向四个方向去扩散
for(int i=0;i<4;i++){
int newx=x+d[i][0];
int newy=y+d[i][1];
if(isArea(newx,newy)){
//[newx,newy]合法
if(grid[newx][newy]==1 && visited[newx][newy]==false){
//[newx,newy]位置陆地,并且未被标记,进行深度优先遍历
//如果已经被标记了,则直接忽略
//找到一个岛,就将整个岛进行标记
area+=dfs(grid,newx,newy);
}
}
}
return area;
}
public int maxAreaOfIsland(int[][] grid) {
m=grid.length;
if(m==0){
return 0;
}
n=grid[0].length;
visited=new boolean[m][n];
int res=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
//有陆地,并且这块陆地未被标记,则就是一个新的岛屿
if(grid[i][j]==1 && visited[i][j]==false){
//使用floodfill算法来标记这个新岛屿岛屿
res=Math.max(res,dfs(grid,i,j));
}
}
}
return res;
}
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
- N 在[1,200]的范围内。
- 对于所有学生,有M[i][i] = 1。
- 如果有M[i][j] = 1,则有M[j][i] = 1。
//思路:深度优先遍历
//1、假设M是(n*n) 矩阵,说明有 n 个人。
//2、遍历编号为 i 的朋友,进行朋友圈标记,是同一个朋友圈的必然会被标记。
//3、是同一个朋友圈,只统计一次。
private int n;
private boolean[] friend;
public int findCircleNum(int[][] M) {
n = M.length;
if( n == 0){
return 0;
}
friend = new boolean[n];
int res = 0;
for(int i=0;i<n;i++){
if(!friend[i]){
res++;
dfs(M,i);
}
}
return res;
}
//遍历编号为 x 的人员的朋友圈,并进行标记
//标记后,只统计一次
private void dfs(int[][] M,int x){
friend[x] = true;
for(int i=0;i<n;i++){
if(i != x){ //看看其他人员是否是朋友
if(M[x][i] == 1 && !friend[i]){ //是朋友,但是还未被标记
dfs(M,i);
}
}
}
}
130. 被围绕的区域
给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。
找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
class Solution {
private int m;
private int n;
//标记边界上的元素是否是'O'
private boolean[][] isO;
private int[][] d={{-1,0},{0,1},{1,0},{0,-1}};
private boolean isArea(int x,int y){
return (x>=0 && x<m) && (y>=0 && y<n);
}
//深度优先遍历,来标记边界 'O'
private void dfs(char[][] board, int x, int y) {
isO[x][y]=true;
for(int i=0;i<4;i++){
int newx=x+d[i][0];
int newy=y+d[i][1];
if(isArea(newx,newy)){
if(board[newx][newy]=='O' && isO[newx][newy]==false){
dfs(board,newx,newy);
}
}
}
}
/**
* 思路:
* 除了和边界有接触的’O'的区域,其他的‘O'的区域都会变成'X'。
* 所以扫描一遍边界,对于在边界上的’O', 通过BFS标记与它相邻的'O'。
*/
public void solve(char[][] board) {
m=board.length;
if(m==0){
return;
}
n=board[0].length;
isO= new boolean[m][n];
//对边界进行深度优先遍历,标记‘O’
for(int j=0;j<n;j++){
if(board[0][j]=='O' && isO[0][j]==false){
dfs(board,0,j);
}
if(board[m-1][j]=='O' && isO[m-1][j]==false){
dfs(board,m-1,j);
}
}
for(int i=0;i<m;i++){
if(board[i][0]=='O' && isO[i][0]==false){
dfs(board,i,0);
}
if(board[i][n-1]=='O' && isO[i][n-1]==false){
dfs(board,i,n-1);
}
}
//对整个数组进行遍历,覆盖非边界的 'O'
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(isO[i][j]==false && board[i][j]=='O'){
board[i][j]='X';
}
}
}
}
}
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:
- 输出坐标的顺序不重要
- m 和 n 都小于150
示例:
给定下面的 5x5 矩阵:
太平洋 ~ ~ ~ ~ ~
~ 1 2 2 3 (5) *
~ 3 2 3 (4) (4) *
~ 2 4 (5) 3 1 *
~ (6) (7) 1 4 5 *
~ (5) 1 1 2 4 *
* * * * * 大西洋
返回:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).
private int m;
private int n;
private boolean[][] pacific;
private boolean[][] atlantic;
//TODO:二维数组四个方向查找的小技巧
private int[][] d={{-1,0},{0,1},{1,0},{0,-1}};
private boolean isArea(int x,int y){
return (x>=0 && x<m) && (y>=0 && y<n);
}
private void dfs(int[][] matrix,boolean[][] visited,int x,int y){
visited[x][y]=true;
for(int i=0;i<4;i++){
int newx=x+d[i][0];
int newy=y+d[i][1];
if(isArea(newx,newy)){
//[newx,newy]地势高,并且未被标记
if(visited[newx][newy]==false && (matrix[newx][newy]>=matrix[x][y])){
dfs(matrix,visited,newx,newy);
}
}
}
}
public List<int[]> pacificAtlantic(int[][] matrix) {
List<int[]> res=new ArrayList<>();
m=matrix.length;
if(m==0){
return res;
}
n=matrix[0].length;
pacific=new boolean[m][n];
atlantic=new boolean[m][n];
for(int i=0;i<m;i++){
dfs(matrix,pacific,i,0);
dfs(matrix,atlantic,i,n-1);
}
for(int j=0;j<n;j++){
dfs(matrix,pacific,0,j);
dfs(matrix,atlantic,m-1,j);
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(pacific[i][j] && atlantic[i][j]){
res.add(new int[]{i,j});
}
}
}
return res;
}
