文章目录
- 题目记录:
- 回溯
- DFS
- 双指针
- 区间调度
- BFS
- 链表
- 前缀和
- 单调栈
- [739. 每日温度](https://leetcode-cn.com/problems/daily-temperatures/)
- [402. 移掉 K 位数字](https://leetcode-cn.com/problems/remove-k-digits/)
- [316. 去除重复字母](https://leetcode-cn.com/problems/remove-duplicate-letters/)
- [239. 滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum/)
- [901. 股票价格跨度](https://leetcode-cn.com/problems/online-stock-span/)
- [962. 最大宽度坡](https://leetcode-cn.com/problems/maximum-width-ramp/)
- 并查集
题目记录:
# 递归
70、509、226、112
# 分治
169、23
#单调栈
A: 739、239、402、316、901、496、581
B: 84、85、503、42、962
# 并查集
547、684、200、924
737、1102、1135、261、1061、323
# BFS
层序遍历:102、103、199、515、637
最短路径:542、994、1162、310
127、139、130、317、505、529、1263、1197、815、934
# DFS
934、1102、685、531、533、332、337、113
(岛屿问题)463、695、827、130
# 滑动窗口
A:76、438、3、567
B:209、1004、1208
C:340、1151、159、1100
# 区间问题:
A:56、986
# 前缀和
560、523、974
# 差分
1094、1109、121、122
253
# 二分查找
240、4、33
# 拓扑排序
210、269
444
# 字符串
5、93、43、227
1055
# 贪心
452、1231、1247、45、621、376
# 字典树
820、123、648、208
# 动态规划
213、1043、416、123、62、63、651、361、1066、750、1230
回溯
模板
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
# 做选择
将该路径从选择列表中移除
路径.add(选择)
backtrack(路径, 选择列表)
# 撤销选择
路径.remove(选择)
将该选择再加入选择列表
排列
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
func permute(nums []int) [][]int {
var res [][]int
used := make([]bool, len(nums))
var path []int
dfs(nums, used, path, &res)
return res
}
func dfs(nums []int, used []bool, path []int, res *[][]int){
// 触发结束条件
if len(path) == len(nums) {
tmp := make([]int, len(nums), len(nums))
copy(tmp, path)
*res = append(*res, tmp)
return
}
for i, v := range nums {
if used[i] {
continue
}
used[i] = true
path = append(path, v)
dfs(nums, used, path, res)
used[i] = false
path = path[:len(path) - 1]
}
}
func main() {
input := []int{1,2,3}
res := permute(input)
fmt.Println(res)
}
47.全排列II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
func permuteUnique(nums []int) [][]int {
var res [][]int
used := make([]bool, len(nums))
dfs(nums, used, []int{}, &res)
return res
}
func dfs(nums []int, used []bool, path []int, res *[][]int) {
if len(path) == len(nums) {
tmp := make([]int, len(nums))
copy(tmp, path)
*res = append(*res, tmp)
return
}
// rep不需要回滚,每一次调用dfs,此处重新分配内存
rep := make(map[int]bool, len(nums))
for i,v := range nums {
if used[i] || rep[v]{
continue
}
rep[v], used[i] = true, true
path = append(path, v)
dfs(nums, used, path, res)
used[i] = false
path = path[:len(path) - 1]
}
}
51.N皇后
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
var res [][]string
func solveNQueens(n int) [][]string {
res = [][]string{} // 使用全局变量时要初始化一遍,否则影响结果
used := make([][]bool, n) // 制造二维bool阵
for i := 0; i < n; i++ {
used[i] = make([]bool, n)
}
trackBack(used, [][]byte{})
return res
}
func trackBack(used [][]bool, path [][]byte) {
if len(path) == len(used) {
t := make([]string, len(path))
for k,bs := range path {
t[k] = string(bs)
}
res = append(res,t)
}
for j := 0; j < len(used); j++ { //此处j代表每一行的第j个元素,也就是列,每个位置轮流检测是否符合条件
if !valid(used, len(path), j) { // 减支,path的行数能够代表已经铺到第几层了
continue
}
bs := getLine(len(used))
bs[j] = 'Q'
used[len(path)][j] = true //棋盘放子
path = append(path,bs) // 加入选择路径
trackBack(used, path) // 递归
path = path[:len(path)-1] // 回溯
used[len(path)][j] = false // used回滚一定要在track回滚之后,len(path)的长度才变化了,否则不是回滚到真正的位置
}
}
func valid(used [][]bool, row, col int) bool { // 只需要校验左、左上、上、右上
var i, j int
for i = 0; i < row; i++ { // 上
if used[i][col] == true {
return false
}
}
for j = 0; j < len(used); j++ { //左
if used[row][j] == true {
return false
}
}
i, j = row, col
for i >= 0 && j >= 0 { // 左上
if used[i][j] == true {
return false
}
i--
j--
}
i,j = row, col
for i >= 0 && j< len(used) { //右上
if used[i][j] == true {
return false
}
j++
i--
}
return true
}
func getLine(n int) []byte{
bs := make([]byte,n)
for i:=0;i<n;i++{
bs[i] = '.'
}
return bs
}
子集
78.子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
func subsets(nums []int) [][]int {
var res [][]int
dfs(nums, 0, []int{}, &res)
return res
}
func dfs(nums []int, start int, path []int, res *[][]int) {
tmp := make([]int, len(path)) // 子集从最少的时候追加,没有退出条件
copy(tmp, path)
*res = append(*res, tmp)
for i := start; i < len(nums); i++ {
path = append(path, nums[i])
dfs(nums, i + 1, path, res)
path = path[:len(path) - 1]
}
}
90.子集 II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
func subsetsWithDup(nums []int) [][]int {
sort.Ints(nums)
var res [][]int
dfs(nums, 0, []int{},&res)
return res
}
func dfs(nums []int, start int, path []int, res *[][]int) {
tmp:= make([]int, len(path))
copy(tmp, path)
*res =append(*res, tmp)
for i:= start; i < len(nums); i++ {
if i > start && nums[i] == nums[i - 1] { //例如{1,2,2},第一个2用过了,就略过第二个2
continue
}
path = append(path, nums[i])
dfs(nums, i + 1, path, res)
path = path[: len(path) - 1]
}
}
组合
77.组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
func combine(n int, k int) [][]int {
var res [][]int
dfs(n, k, 1, []int{}, &res) // 从1开始
return res
}
func dfs(n, k, start int, path []int, res *[][]int) {
if len(path) == k {
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
return
}
for i := start; i <= n; i++ {
path = append(path, i)
dfs(n, k, i + 1, path, res) // 同个元素不能重复使用,传i + 1进去
path = path[: len(path) - 1]
}
}
39.组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
func combinationSum(candidates []int, target int) [][]int {
var res [][]int
dfs(candidates, 0, []int{}, target, &res)
return res
}
func dfs(candidates []int, start int, path []int, target int,res *[][]int) {
if target == 0 {
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
return
}
for i := start; i < len(candidates); i++ {
if target < candidates[i] {
continue
}
path = append(path, candidates[i])
// 同个元素可以重复使用,传i进去
// targrt 减去当前元素值,减少求和的步骤
dfs(candidates, i, path, target - candidates[i], res)
path = path[: len(path) - 1]
}
}
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
func combinationSum2(candidates []int, target int) [][]int {
sort.Ints(candidates)
var res [][]int
dfs(candidates, 0, target, []int{}, &res)
return res
}
func dfs(candidates []int, start int, target int, path []int, res *[][]int) {
if target == 0 {
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
return
}
for i := start; i < len(candidates); i++ {
if i > start && candidates[i] == candidates[i - 1] {
continue
}
if target < candidates[i] {
return
}
path = append(path, candidates[i])
dfs(candidates, i + 1, target - candidates[i], path, res)
path = path[: len(path) - 1]
}
}
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
func generateParenthesis(n int) []string {
var res []string
dfs(n, n, []byte{}, &res)
return res
}
func dfs(left, right int, path []byte, res *[]string) { // left、right 剩下的括号数量
if left > right { // 剩下的左括号多说明一定错误
return
}
if left < 0 || right < 0 {
return
}
if left == 0 && right == 0 {
*res = append(*res, string(path))
return
}
path = append(path, '(')
dfs(left - 1, right, path, res)
path = path[: len(path) - 1]
path = append(path, ')')
dfs(left, right - 1, path, res)
path = path[: len(path) - 1]
}
DFS
双指针
左右指针
快慢指针
滑动窗口
模板
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
76.最小覆盖子串
func minWindow(s string, t string) string {
left, right, start, end, minLen := 0,0,0,0,math.MaxInt64
windows, needs := make(map[byte]int, len(s)), make(map[byte]int, len(t))
valid := 0
tt := []byte(t) //这里要转成byte数组才能匹配上
for _, v := range tt {
needs[v]++
}
for right < len(s) {
ch := s[right]
right++
if _,ok := needs[ch]; ok {
windows[ch]++
if windows[ch] == needs[ch] {
valid++
}
}
for valid == len(needs) {
if right - left < minLen { // 注意一旦匹配上就要先更新长度
start,end = left, right
minLen = right - left
}
dd := s[left]
left++
if _,ok := needs[dd]; ok {
// 此处要先扣除valid的值,否则windows更新完一定会匹配不上
if windows[dd] == needs[dd] {
valid--
}
windows[dd]--
}
}
}
if minLen == math.MaxInt64{
return ""
}
return s[start: end]
}
438. 找到字符串中所有字母异位词
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
func findAnagrams(s string, p string) []int {
res := make([]int, 0)
left, right, valid := 0,0,0
windows, needs := make(map[byte]int, len(s)), make(map[byte]int, len(p))
pp := []byte(p)
for _, v := range pp {
needs[v]++
}
for right < len(s) {
ch := s[right]
if _,ok := needs[ch]; ok {
windows[ch]++
if windows[ch] == needs[ch] {
valid++
}
}
right++
for valid == len(needs){
if right - left == len(pp) {
res = append(res, left)
}
dd := s[left]
if _,ok := needs[dd]; ok {
if windows[dd] == needs[dd] {
valid--
}
windows[dd]--
}
left++
}
}
return res
}
3. 无重复字符的最长子串
func lengthOfLongestSubstring(s string) int {
if len(s) == 0 || len(s) == 1 {
return len(s)
}
str := []byte(s)
window := make(map[byte]int, len(s))
left, right, res := 0,0,0
for right < len(str) {
cur := str[right]
right++
window[cur]++
for window[cur] > 1{
last := str[left]
left++
window[last]--
}
res = max(res, right - left)
}
return res
}
func max(a int, b int) int {
if a > b {
return a
}
return b
}
567. 字符串的排列
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
func checkInclusion(s1 string, s2 string) bool {
left, right, valid := 0,0,0
windows,needs := make(map[byte]int, len(s2)), make(map[byte]int, len(s1))
str := []byte(s1)
for _, v := range str{
needs[v]++
}
for right < len(s2) {
cur := s2[right]
right++
if _, ok := needs[cur]; ok {
windows[cur]++
if windows[cur] == needs[cur] {
valid++
}
}
for valid == len(needs) {
if len(s1) == right - left {
return true
}
dd := s2[left]
left++
if _,ok := needs[dd];ok {
if windows[dd] == needs[dd] {
valid--
}
windows[dd]--
}
}
}
return false
}
区间调度
区间合并
56. 合并区间
给出一个区间的集合,请合并所有重叠的区间
1. 排序
2. 插入第一个
3. 依次判断第二个值是否小于res中的第二个值(注意res长度)
func merge(intervals [][]int) [][]int {
// 这里一定要判,否则下面会panic
if len(intervals) <= 1 {
return intervals
}
res := make([][]int, 0, len(intervals))
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
res = append(res, intervals[0])
for i := 1; i < len(intervals); i++ {
if intervals[i][0] <= res[len(res) -1][1] {
res[len(res) -1][1] = max(res[len(res) -1][1],intervals[i][1])
} else {
res = append(res, intervals[i])
}
}
return res
}
区间交集
986. 区间列表的交集
func intervalIntersection(A [][]int, B [][]int) [][]int {
i,j := 0,0
res := make([][]int, 0, len(A) + len(B))
for i < len(A) && j < len(B) {
a1,a2 := A[i][0], A[i][1]
b1,b2 := B[j][0], B[j][1]
if a1 <= b2 && a2 >= b1 {
res = append(res, []int{max(a1, b1), min(a2, b2)})
}
if b2 < a2 {
j++
} else {
i++
}
}
return res
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
BFS
https://mp.weixin.qq.com/s?__biz=MzA5ODk3ODA4OQ==&mid=2648167212&idx=1&sn=6af5ffe5b69075b21bb4743ddcee4e7c&chksm=88aa236abfddaa7cae70b42edb299d0a52d9f1cc4fc1fdba1116972fc0ca0275b8bfdf10851b&token=1607921395&lang=zh_CN#rd
应用场景:层序遍历(102)、最短路径(1162)
102. 二叉树的层序遍历
func levelOrder(root *TreeNode) [][]int {
res := make([][]int, 0)
if root == nil {
return res
}
queue := make([]*TreeNode, 0)
queue = append(queue, root)
for len(queue) > 0 {
var tmp []*TreeNode
nodeValue := make([]int, 0)
for _,v := range queue {
if v == nil {
continue
}
if v.Left != nil {
tmp = append(tmp, v.Left)
}
if v.Right != nil {
tmp = append(tmp, v.Right)
}
nodeValue = append(nodeValue,v.Val)
}
res = append(res, nodeValue)
queue = tmp
}
return res
}
1162 地图分析
你现在手里有一份大小为 N x N 的「地图」(网格) grid,上面的每个「区域」(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋区域,这个海洋区域到离它最近的陆地区域的距离是最大的。
我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。
如果我们的地图上只有陆地或者海洋,请返回 -1。
func maxDistance(grid [][]int) int {
queue := []*point{}
// 首先把最初始的陆地加到辅助队列里
for i := 0; i < len(grid); i++ {
for j := 0; j < len(grid[0]); j++ {
if grid[i][j] == 1 {
queue = append(queue, &point{x: i, y: j})
}
}
}
if len(queue) == 0 || len(queue) == len(grid) * len(grid[0]) {
return -1
}
dx := [4]int{1, 0, -1, 0}
dy := [4]int{0, -1, 0, 1}
// 初始距离设为-1, 因为进入第一层就加过1了
distancce := -1
for len(queue) > 0 {
distancce++
//tmp用来装queue中所有点下一次会污染到的上下左右的区域
tmp := []*point{}
for _, v := range queue {
for i := 0; i < 4; i++ {
tx := v.x + dx[i]
ty := v.y + dy[i]
// 不符合要求的掠略过
if tx < 0 || tx >= len(grid) ||
ty < 0 || ty >= len(grid[0]) ||
grid[tx][ty] != 0 {
continue
}
tmp = append(tmp, &point{tx, ty})
grid[tx][ty] = 2
}
}
// 遍历完一边queue了,下一次要遍历被污染到的区域
queue = tmp
}
return distancce
}
type point struct{
x,y int
}
链表
type ListNode struct {
Val int
Next *ListNode
}
反转链表
反转链表
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
newHead := reverseList(head.Next)
head.Next.Next = head
head.Next = nil
return newHead
}
反转链表前 N 个节点
func reverseList(head *ListNode, n int) *ListNode {
if n == 1 {
successor = head.Next
return head
}
newHead := reverseList(head.Next, n-1)
head.Next.Next = head
head.Next = successor
return newHead
}
反转从位置 m 到 n 的链表
var successor *ListNode
func reverseList(head *ListNode, n int) *ListNode {
if n == 1 {
successor = head.Next
return head
}
newHead := reverseList(head.Next, n-1)
head.Next.Next = head
head.Next = successor
return newHead
}
func reverseBetween(head *ListNode, left int, right int) *ListNode {
if left == 1 {
return reverseList(head, right)
}
head.Next = reverseBetween(head.Next, left - 1, right - 1)
return head
}
K个一组翻转链表
func reverseKGroup(head *ListNode, k int) *ListNode {
if head == nil {
return head
}
a,b := head, head
for i := 0; i < k; i++ {
if (b == nil) {
return head
}
b = b.Next
}
new := reverse(a, b)
a.Next = reverseKGroup(b, k)
return new
}
func reverse(a *ListNode, b *ListNode) *ListNode {
var pre *ListNode
cur, next := a, a
for cur != b {
next = cur.Next
cur.Next = pre
pre = cur
cur = next
}
return pre
}
前缀和
单调栈
739. 每日温度
解法:单调递增栈
func dailyTemperatures(temperatures []int) []int {
stack := make([]int, 0, len(temperatures))
res := make([]int, len(temperatures), len(temperatures))
for i := len(temperatures) - 1;i >= 0; i-- {
// 栈里面存的是索引值
for len(stack) > 0 && temperatures[i] >= temperatures[stack[len(stack) -1]] {
stack = stack[:len(stack) - 1]
}
// 栈里面最上面一个存的是最近的一个比当前值大的数
if len(stack) > 0 {
res[i] = stack[len(stack) - 1] - i
}
stack = append(stack, i)
}
return res
}
402. 移掉 K 位数字
func removeKdigits(num string, k int) string {
res := make([]byte, 0, len(num)-k)
for i := 0; i < len(num); i++ {
for k > 0 && len(res) > 0 && num[i] < res[len(res)-1] {
res = res[:len(res)-1]
k--
}
res = append(res, num[i])
}
// 一定要下面这步,否则k=1时会跳过
res = res[:len(res)-k]
ans := strings.TrimLeft(string(res), "0")
if ans == "" {
ans = "0"
}
return ans
}
316. 去除重复字母
func removeDuplicateLetters(s string) string {
if len(s) <= 1 {
return s
}
// 记录字符总出现次数
tmp := make(map[byte]int, len(s))
for i := 0; i < len(s); i++ {
tmp[s[i]]++
}
// 记录是否出现在栈中
inStack := make(map[byte]bool, len(s))
// 栈,用于记录最终结果
stack := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
// 无论是否入栈,该值出现后就应减去1次出现次数
tmp[s[i]]--
// 在栈中就不用参与比较了,也不能继续入栈了
if inStack[s[i]] {
continue
}
// 栈中元素必须大于0,该值必须小于栈末尾元素,栈末尾必须以后还能出现,此时循环让栈顶元素出栈
for len(stack) > 0 && s[i] < stack[len(stack)-1] && tmp[stack[len(stack)-1]] > 0 {
// 要出栈,说明栈中已经不存在该元素了
inStack[stack[len(stack)-1]] = false
// 出栈动作
stack = stack[:len(stack) - 1]
}
// 让其他人出栈了,自己进栈
stack = append(stack, s[i])
// 证明自己进栈了
inStack[s[i]] = true
}
return string(stack)
}
239. 滑动窗口最大值
解法:单调递减栈
func maxSlidingWindow(nums []int, k int) []int {
stack := make([]int, 0 , len(nums))
res := make([]int, 0 , len(nums) - k + 1)
for i := 0; i < len(nums); i++ {
// 栈里面存储的是(当前最大数的)索引值
for len(stack) > 0 && nums[i] >= nums[stack[len(stack) - 1]] {
stack = stack[:len(stack) - 1]
}
stack = append(stack, i)
// i-k 是左边界下标
if i-k+1 > stack[0] {
// 窗口已越过栈首元素(下标值),出栈
stack = stack[1:]
}
// 要开始存储结果了
if i+1 >= k {
res = append(res, nums[stack[0]])
}
}
return res
}
901. 股票价格跨度
解法:单调递减栈
type StockSpanner struct {
// 单调递减栈
stack []int
// 栈对应的小于等于自己的天数
days []int
}
func Constructor() StockSpanner {
return StockSpanner{
stack:make([]int, 0, 10000),
days:make([]int, 0, 10000),
}
}
func (this *StockSpanner) Next(price int) int {
// 鉴于等于,本来就算一天
day := 1
for len(this.stack) > 0 && price >= this.stack[len(this.stack) - 1] {
// 先加上即将出栈的数值对应的days
day += this.days[len(this.days) - 1]
// 栈及对应的天数一起出去
this.stack = this.stack[:len(this.stack) - 1]
this.days = this.days[:len(this.days) - 1]
}
this.stack = append(this.stack, price)
this.days = append(this.days, day)
return day
}
962. 最大宽度坡
解法:
- 制作一个以nums[0]开头的单调递减栈,因为nums[0]一定要在,所以使用逐个判断append的方式
- nums从后往前逐个与栈中末尾元素对比,直到找到比自己大的元素(每一个都记录距离)
func maxWidthRamp(nums []int) int {
stack := make([]int, 0, len(nums))
// 完成以nums[0]开头的单调递减栈
for i := 0; i < len(nums); i++ {
if len(stack) == 0 || nums[i] < nums[stack[len(stack) - 1]] {
stack = append(stack, i)
}
}
res := 0
// 从后往前逐一与栈首元素逐一比较
for i := len(nums) - 1 ; i >= 0; i-- {
for len(stack) > 0 && nums[i] >= nums[stack[len(stack) - 1]] {
res = max(res, i - stack[len(stack) - 1])
stack = stack[: len(stack) - 1]
}
}
return res
}
func max(a,b int) int {
if a > b {
return a
}
return b
}
并查集
1202. 交换字符串中的元素
func smallestStringWithSwaps(s string, pairs [][]int) string {
strLength := len(s)
parent, rank := make([]int, strLength), make([]int, strLength)
for i := range parent{
parent[i] = i
rank[i] = 1
}
var find func(int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
union := func(from,to int) {
rootX := find(from)
rootY := find(to)
if rootX == rootY {
return
}
if rank[rootX] < rank[rootY] {
rootX, rootY = rootY, rootX
}
rank[rootX] += rank[rootY]
parent[rootY] = rootX
}
for _, row := range pairs {
union(row[0], row[1])
}
groups := make(map[int][]byte, strLength)
for i := range s{
index := find(i)
groups[index] = append(groups[index], s[i])
}
for _, bytes := range groups {
sort.Slice(bytes, func(i, j int) bool { return bytes[i] < bytes[j] })
}
ans := make([]byte, strLength)
for i := range ans {
f := find(i)
ans[i] = groups[f][0]
groups[f] = groups[f][1:]
}
return string(ans)
}