本周刷的题有选择地写了题解,前半周写了很多二分查找问题和一些其他随机题目(闲来无事),后半周主要集中做顺序表的前缀和类型的题目。
犯傻了!!没有仔细审题,题目里是本身有序的,其实就是简单前缀和加前后不同情况分类讨论,前边的一定小于当前nums[i],所以绝对值差之和就是(i+1)*nums[i]-presum[i];后边的值大于当前nums[i],绝对值差之和就是presum[nums.length-1]-presum[i]-nums[i]*(nums.length-1-i);两者相加就可!代码如下:
class Solution {
public int[] getSumAbsoluteDifferences(int[] nums) {
int sum = 0;
int[] prefixSum = new int[nums.length];
//计算前缀和
for(int i=0;i<nums.length;i++){
sum += nums[i];
prefixSum[i] = sum;
}
//计算每个数的差绝对值之和
int[] output = new int[nums.length];
for(int i=0;i<nums.length;i++){
// int sumOfLeftDifferences = (i+1)*nums[i]-prefixSum[i];
// int sumOfRightDifferences = prefixSum[nums.length-1]-prefixSum[i]-nums[i]*(nums.length-1-i);
// sumOfDifferences = sumOfLeftDifferences+sumOfRightDifferences;
output[i] = (i+1)*nums[i]-prefixSum[i]+ prefixSum[nums.length-1]-prefixSum[i]-nums[i]*(nums.length-1-i);
}
return output;
}
}
前缀和+排序,就是暴力解决的暂时也没有其他好思路。(简单浏览了一下其他人题解,貌似有双指针+二分查找解决)
class Solution {
public int rangeSum(int[] nums, int n, int left, int right) {
int[]sum=new int[n*(n+1)/2];
int index=0;
final int length=(int)1e9+7;//最大限度;
for(int i=0;i<nums.length;i++){
int s=0;
for(int j=i;j<nums.length;j++){
s+=nums[j];
sum[index++]=s;
}
}
Arrays.sort(sum);
int ans=0;
for(int i=left-1;i<right;i++){
ans=(ans+sum[i])%length;
}
return ans;
}
}
前缀和加滑动窗口(掌握的不是很好,主要没接触过只做过类似的有一题提到了滑动窗口,当时也没细看,知识点还是快速掌握的好)。这题用了逆向思维,要获得外边k个的最大点数和,我们用一个滑动窗口length-k,只要保证我们窗口内最小,那么外边一定最大。
class Solution {
public int maxScore(int[] cardPoints, int k) {
int len=cardPoints.length,sum=0;
for(int cardPoint:cardPoints){
sum+=cardPoint;//前缀和
}
int min=Integer.MAX_VALUE,temp=0;
int length=len-k;
for(int i=0;i<len;i++){
temp+=cardPoints[i];
if(i>=length){//如果大于窗口范围
temp-=cardPoints[i-length];//减去最外边的值,也就是窗口进行了滑动
}
if(i>=length-1)
min=Math.min(min,temp);
}
return sum-min;
}
}
这题看似平常其实有好几个坑,都被我踩中了.....
首先这个连续的子数组要大于等于2。将这个数组连续前缀和算出,连续的子数组和肯定是不同前缀和相减,如果两个前缀和的余数相等,那么两个相减就是k的倍数(记住两个前缀和相差要大于1)
代码如下:
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
if(nums.length<2) return false;//长度小于2,直接返回false
for(int i=0;i<nums.length-1;i++){
if(nums[i]==0&&nums[i+1]==0) return true;//有前缀和为0,返回true
}
if(k==0) return false;
if(k<0) k=-k;
Map<Integer,Integer>map=new HashMap<>();//用一个哈希表储存位置
map.put(0,-1);
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
int mod=sum%k;
if(map.containsKey(mod)){//mod相等
if(i-map.get(mod)>1) return true;
}
else
map.put(mod,i);//否则放入哈希表
}
return false;
}
}
首先我自己做的思路就是找到左右两边最高的柱子中较小的柱子就是基准,用双指针实施,但具体细节方面有很多错误。然后思考了一下栈的运用,其实只要后边的比前边的低,并且再后边有高的柱子就能储水。
下边是双指针的官方题解:
其实就是看形成低洼的条件
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;//左右两个指针
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {//较小的在左边
ans += leftMax - height[left];
++left;
} else {//较小的在右边
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
贪心算法,注意各种结构的调用!
class Solution {
public List<List<Integer>> groupThePeople(int[] groupSizes) {
HashMap<Integer,Integer>count=new HashMap<Integer,Integer>();
for(int i=0;i<groupSizes.length;i++){
int size=groupSizes[i];
count.put(size,count.getOrDefault(size,0)+1);
}
List<List<Integer>>result=new ArrayList<>();
for(Map.Entry<Integer,Integer>entry:count.entrySet()){
Integer key=entry.getKey();
Integer value=entry.getValue();
while(value>=key){
List<Integer>list=new ArrayList<>();
for(int i=0;i<groupSizes.length;i++){
if(groupSizes[i]==key){
list.add(i);
groupSizes[i]=-1;
if(list.size()==key){
break;
}
}
}
result.add(list);
value-=key;
}
}
return result;
}
}
#1608 特殊数组的特征值 |
找特殊数组的特征值,首先思路是找到数组的最大值,用一个新数组记录原数组每个数字的个数,从最大值处往0开始相加个数,如果相加的个数=该数字,证明就是特殊值,如果遍历完没有就返回-1;
class Solution {
public int specialArray(int[] nums) {
int maxval=0;//设定最大值
for(int x:nums){
if(x>maxval) maxval=x;//找最大值
}
int count[]=new int[maxval+1];//记数
for(int x:nums){
count[x]++;
}
int sum=0;
for(int i=maxval;i>=0;i--){
sum+=count[i];//sum就是大于i的个数
if(sum==i) return i;//相等返回特殊值
}
return -1;
}
}
明白异或运算的特性,异或满足交换律结合律,同时一个数和他本身异或为0,其他数和0异或还是本身,所以直接从头异或到尾得出的就是单一元素。(异或方法有序无序都可以用)
class Solution {
public int singleNonDuplicate(int[] nums) {
int ans=nums[0];
for(int i=1;i<nums.length;i++){
ans^=nums[i];
}
return ans;
}
}
法二:二分查找法
才发现有序数组,并且规定了O(log n)
时间复杂度和 O(1)
空间复杂度。每个数都是两个,只有一个数唯一,则这个数左边必定是偶数个,右边也是偶数个,且左边肯定是nums[偶数]=nums[偶数+1],右边肯定是nums[奇数]=nums[奇数+1],所以二分如果nums[mid]==nums[mid^1](就是如果mid是偶数,nums[mid]==nums[mid+1],如果是奇数,nums[mid]==nums[mid-1])那么把left往右调,left=mid+1;(因为满足这种情况,证明单一的数必在右边)否则 right=mid;(在左边)
class Solution {
public int singleNonDuplicate(int[] nums) {
int l=0,r=nums.length-1;
while(l<r){
int mid=l+(r-l)/2;
if(nums[mid]==nums[mid^1]) l=mid+1;
else r=mid;
}
return nums[l];
}
}
用两个集合存储两个数组的值,然后在用集合数小的那个遍历,看另外一个集合是否有相同元素,如果有加入answer集合;
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new HashSet<Integer>();
for (int num : nums1) {
set1.add(num);
}
for (int num : nums2) {
set2.add(num);
}
return getIntersection(set1, set2);
}
public int[] getIntersection(Set<Integer> set1, Set<Integer> set2) {
if (set1.size() > set2.size()) {
return getIntersection(set2, set1);
}
Set<Integer> intersectionSet = new HashSet<Integer>();
for (int num : set1) {
if (set2.contains(num)) {
intersectionSet.add(num);
}
}
int[] answer = new int[intersectionSet.size()];
int index = 0;
for (int num : intersectionSet) {
answer[index++] = num;
}
return answer;
}
}
#4 寻找两个正序数组的中位数 (好题)
首先题目要求算法的时间复杂度应该为 O(log (m+n))
。我想到了二分法,怎么二分呢?中位数就是分割一串数字的数,中位数左边都小于中位数,中位数右边都大于中位数,我们要找到分割线,满足nums1,nums2左边最大值小于nums1,nums2右边最小值,那分割线上就是我们要找的中位数,设nums1上分割线位置为i,nums2上分割线位置为j,则i+j=(m+n+1)/2(合并了奇偶项保证中位数总在左边),然后进行二分查找,要满足nums1[i-1]<nums2[j]&&nums2[j-1]<nums1[i](i、j左右分别为小与大),所以不满足这个条件就缩小范围。最后找到分割线解决问题!
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m=nums1.length,n=nums2.length;
if(m>n) return findMedianSortedArrays(nums2,nums1);//以短的数组开始
int total=(m+n+1)/2,left=0,right=m;
while(left<right){
int i=left+(right-left+1)/2;
int j=total-i;
if(nums1[i-1]>nums2[j]) right=i-1;//证明在i的左边
else left=i;
}
int i=left,j=total-i;
int nums1Leftmax=i==0?Integer.MIN_VALUE:nums1[i-1];//防止左边无的情况
int nums2leftmax=j==0?Integer.MIN_VALUE:nums2[j-1];
int num1rightmin=i==m?Integer.MAX_VALUE:nums1[i];
int nums2rightmin=j==n?Integer.MAX_VALUE:nums2[j];
if((m+n)%2==1) return Math.max(nums1Leftmax,nums2leftmax);
else return (double)(Math.max(nums1Leftmax,nums2leftmax)+Math.min(num1rightmin,nums2rightmin))/2;
}
}
首先我想到的是直接模拟求解
class Solution {
public int findTheDistanceValue(int[] arr1, int[] arr2, int d) {
int ans=0;
for(int i=0;i<arr1.length;i++){
int l=arr1[i]-d,r=arr1[i]+d,count=0;
for(int j=0;j<arr2.length;j++){
if(arr2[j]<l||arr2[j]>r) count++;
}
if(count==arr2.length){
ans+=1;
}
}
return ans;
}
}
然后感觉可以二分解决,对arr1中的数,先对arr2排序,二分找到大于等于x的第一个数和小于x的第一个数,如果此时大于d,则其他必成立,此时小于d则删除。
class Solution {
public int findTheDistanceValue(int[] arr1, int[] arr2, int d) {
int ans=0,m=arr1.length,n=arr2.length;
Arrays.sort(arr2);//对arr2排序
for(int x:arr1){
int num=binarySearch(x,arr2);//二分查找最接近x的数
boolean flag=true;
if(num<n){
flag&=arr2[num]-x>d;
}
if(num-1>=0&&num-1<=n){
flag&=x-arr2[num-1]>d;
}
ans+=flag?1:0;//true就加1
}
return ans;
}
public int binarySearch(int x,int[]arr){
int l=0,r=arr.length-1;
if(x>arr[r]) return r+1;
while(l<r){
int mid=(r-l)/2+l;
if(arr[mid]<x) l=mid+1;
else r=mid;
}
return l;
}
}
贪心算法:找到每个数字右边最大的数(必须大于这个数字) 然后返回,但是细节方面有很多要考虑的地方,提交了2次,第三次才全部考虑完全!可恶
class Solution {
public int maximumSwap(int num) {
String s=String.valueOf(num);
char[]string=s.toCharArray();
int[][]count=new int[s.length()][1];//用一个数组记录右边最大数字位置
for(int i=0;i<s.length();i++){
count[i][0]=findmax(string,string[i],i);//统计右边最大数字位置没有就为0
if(count[i][0]!=0){
int pos=count[i][0];
if(string[i]!=string[pos]){//两个数字如果相等交换其实没有意义!
char temp=string[i];
string[i]=string[pos];
string[pos]=temp;
s=String.valueOf(string);
num=Integer.valueOf(s);
return num;
}
}
}
return num;
}
public int findmax(char[]string,char target,int start){
int pos=0;
char max=target;
for(int i=start;i<string.length;i++){
if(string[i]>=max){
max=string[i];
pos=i;
}
}
return pos;
}
}
困难题,但思路很简洁,第一次遍历,让位置上的数字等于位置+1;第二次遍历,如果位置上的数!=位置+1,那么这个地方就是缺失的第一个正数,如果没有的话,就返回nums.length+1(证明原数组有1,2,3......nums.length)代码如下
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
int temp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = temp;
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
}