【力扣竞赛题解】第 331 场周赛

85 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

竞赛题目链接:leetcode.cn/contest/wee…

A.从数量最多的堆取走礼物

简化题意:一堆数字,重复k次:将最大数替换为其取根号(向下取整),最后的和为多少。

模拟

首先比较直接的思路是我们去按照题意模拟,也就是遍历一遍,找到最大值,将其修改为平方根,重复k次即可,答案就是最后再求和。 时间复杂度O(kn),这里k和n都是10^3,符合要求。

排序

如果想省事,也可以直接调用sort,修改值,再调用sort,重复k次。 时间复杂度O(knlogn),这里k和n都是10^3,符合要求。

这道题是重复地取最大值和放入其平方根。很符合堆排序的场景,建立最大堆,每次取数复杂度O(logn),插入复杂度O(logn),整体复杂度O(klogn)

package main

import (
	"container/heap"
	"math"
)

type IntHeap []int

func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
	*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() (v interface{}) {
	*h, v = (*h)[:h.Len()-1], (*h)[h.Len()-1]
	return
}

func pickGifts(gifts []int, k int) int64 {
	h := &IntHeap{}
	heap.Init(h)
	for i := 0; i < len(gifts); i++ {
		heap.Push(h, gifts[i])
	}

	for i := 0; i < k; i++ {
		t := heap.Pop(h)
		t = int(math.Sqrt(float64(t.(int))))
		heap.Push(h, t)
	}
	ans := int64(0)
	for i := 0; i < len(gifts); i++ {
		ans += int64(heap.Pop(h).(int))
	}

	return ans
}

B.统计范围内的元音字符串数

题意简化:给你字符串数组,k次询问从下标l到r有多少个字符串的首位单词都是元音字母。

枚举

暴力对lr内每个字符串判断。 时间复杂度O(kn),k和n都是10^5,超时。

前缀和

创建一个数组m,记录从0到i有几个字符串符合条件,对lr区间的答案就是m[r]-m[l-1] 时间复杂度O(k)

func vowelStrings(words []string, queries [][]int) []int {
   m := make([]int, len(words)+1)
   for i := 0; i < len(words); i++ {
      if (words[i][0] == 'a' || words[i][0] == 'e' || words[i][0] == 'i' || words[i][0] == 'o' || words[i][0] == 'u') && (words[i][len(words[i])-1] == 'a' || words[i][len(words[i])-1] == 'e' || words[i][len(words[i])-1] == 'i' || words[i][len(words[i])-1] == 'o' || words[i][len(words[i])-1] == 'u') {
         m[i+1] = m[i] + 1
      } else {
         m[i+1] = m[i]
      }
   }

   ans := make([]int, len(queries))
   for i := 0; i < len(queries); i++ {
      ans[i] = m[queries[i][1]+1] - m[queries[i][0]]
   }
   return ans
}

C.打家劫舍 IV

题目简化:给你一个数组,你从中取最少k个数(不能取连续两个数),问取到的数的最大值最小是多少。

二分答案

题目很明确地是一个最大值最小化的问题,这类题型通常可以用二分答案的思路。对于取到的数的最大值,在这个数比较小时难以满足,在这个数比较大是可以满足的,也就是满足二分的单调性。对这个最大值进行二分即可得到答案。 时间复杂度O(nlogn)

package main

import "sort"

func judge(nums []int, k, top int) bool {
   l := false
   ans := 0
   for i := 0; i < len(nums); i++ {
      if l == true {
         l = false
         continue
      }
      if nums[i] <= top {
         ans++
         l = true
      } else {
         l = false
      }
   }
   return ans >= k
}

func minCapability(nums []int, k int) int {
   cp := make([]int, len(nums))
   copy(cp, nums)
   sort.Ints(cp)
   l, r := cp[0], cp[len(cp)-1]
   for l < r {
      mid := (l + r) / 2
      if !judge(nums, k, mid) {
         l = mid + 1
      } else {
         r = mid
      }
   }
   return l
}

D.重排水果

简化题意:给你两个长度相等的数组,想要这两个数组每个数出现的次数相同,每次可以交换任意两个数,交换的代价为这两个数的最小值,问代价和最小为多少。

题目分析

首先我们要知道哪些数是要交换的:假设对于k在两个数组共出现m次,如果第一个数组的k大于m/2个,那么这些数就是要交换出去的。 很明显当m为奇数不可能成立,直接返回-1.

贪心

有了些待交换的数,如何交换效率最优?

  1. 贪心的想法:把这个数组待交换的最小值与另一个数组待交换的最大的交换
  2. 一个特殊情况:可以把非待交换数与待交换数交换,相当于工具人,贪心的思路是直接取最小数交换即可。 时间复杂度O(nlogn)
package main

import (
	"math"
	"sort"
)

func minCost(basket1 []int, basket2 []int) int64 {
	m := make(map[int]int, 100005)
	m1 := make(map[int]int, 100005)
	m2 := make(map[int]int, 100005)
	minn := math.MaxInt
	for i := 0; i < len(basket1); i++ {
		m[basket1[i]]++
		m[basket2[i]]++
		m1[basket1[i]]++
		m2[basket2[i]]++
	}
	for k, v := range m {
		if v%2 == 1 {
			return -1
		} else if k < minn {
			minn = k
		}
	}
	l1, l2 := make([]struct {
		value int
		num   int
	}, 0), make([]struct {
		value int
		num   int
	}, 0)
	for k, v := range m1 {
		if v > m[k]/2 {
			l1 = append(l1, struct {
				value int
				num   int
			}{value: k, num: v - m[k]/2})
		}
	}
	for k, v := range m2 {
		if v > m[k]/2 {
			l2 = append(l2, struct {
				value int
				num   int
			}{value: k, num: v - m[k]/2})
		}
	}

	sort.Slice(l1, func(i, j int) bool {
		return l1[i].value < l1[j].value
	})
	sort.Slice(l2, func(i, j int) bool {
		return l2[i].value < l2[j].value
	})

	ans := int64(0)
	// 情况1
	for len(l1) > 0 && len(l2) > 0 {
		if l1[0].value <= minn*2 || l2[0].value <= minn*2 {
			if l1[0].value < l2[0].value {
				num := min(l1[0].num, l2[len(l2)-1].num)
				ans += int64(num) * int64(l1[0].value)
				l1[0].num -= num
				l2[len(l2)-1].num -= num
			} else {
				num := min(l2[0].num, l1[len(l1)-1].num)
				ans += int64(num) * int64(l2[0].value)
				l2[0].num -= num
				l1[len(l1)-1].num -= num
			}
			if len(l1) > 0 && l1[0].num == 0 {
				l1 = l1[1:]
			}
			if len(l1) > 0 && l1[len(l1)-1].num == 0 {
				l1 = l1[:len(l1)-1]
			}
			if len(l2) > 0 && l2[0].num == 0 {
				l2 = l2[1:]
			}
			if len(l2) > 0 && l2[len(l2)-1].num == 0 {
				l2 = l2[:len(l2)-1]
			}
		} else {
			break
		}

	}
	// 情况2
	for len(l1) > 0 {
		ans += int64(minn) * int64(l1[0].num)
		l1 = l1[1:]
	}
	for len(l2) > 0 {
		ans += int64(minn) * int64(l2[0].num)
		l2 = l2[1:]
	}


	return ans

}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

作者本人在最后一题中的特殊情况卡住了,第一发wa之后注意到还有这样的特殊情况,也想到了用非待交换数进行交换,但是脑子一根筋还以为一个数交换后就不用再次交换了,导致最后一题没写出来。好在前三题比较顺利名次对标竞赛积分2100左右

image.png