【leetcode】推多米诺

264 阅读1分钟

题目

原文链接:leetcode-cn.com/problems/pu…

读题

  1. 模拟多米诺骨牌的推倒过程

  2. 本题目中多个推倒同时发生

  3. 如果骨牌同时受左右两个方向的推到,则不会倒下

  4. 倒下或者正在倒下的骨牌不会相互影响

  5. 骨牌的推倒过程是以秒为单位的(这说明整个过程会有多个中间态)

  6. 题目要求输出最终的骨牌状态

解题

解题思路

  1. 根据题意,只有状态为“.”的骨牌会受到影响,因此可以遍历骨牌,对每个“.”状态的骨牌进行受力分析。
  2. 不断遍历,如果遍历中发现没有新的骨牌倒下,则说明推倒过程结束

go解答

package main

import (
	"fmt"
)

func pushDominoes(dominoes string) string {
	arr := []byte(dominoes)
	l := len(arr)
	if l == 1 {
		return dominoes
	}
	// 在本方法中,arr[i-1]会被修改,因此需要提前记住
	var left, current byte
	for {
		hasChange := false
		for i := 0; i < l; i++ {
			left = current
			current = arr[i]
			if arr[i] != '.' {
				continue
			}
			if i == 0 && arr[1] == 'L' {
				arr[i] = 'L'
				hasChange = true
			}

			if i == l-1 && left == 'R' {
				arr[i] = 'R'
				hasChange = true
			}
			if 0 < i && i < l-1 {
				if left == 'R' && arr[i+1] != 'L' {
					arr[i] = 'R'
					hasChange = true
				} else if left != 'R' && arr[i+1] == 'L' {
					arr[i] = 'L'
					hasChange = true
				}
			}
		}
		if !hasChange {
			break
		}
	}
	return string(arr)
}

func main() {
	s := "R.R.L"
	fmt.Println(pushDominoes(s))
}

反思与总结

我上面的方法明显存在很多的改进空间,比如每次循环都会遍历整个数组,事实上可能某一部分已经处理好了,不用每次都去重复遍历。如果输入时这样的“R..........”,我这边的方法效率会很低,每次循环都只处理了一个骨牌。

官方解题方法中,第二种方法也是跟我使用一样的模拟思路,不过官方在细节上处理的更好。大概思路是找出一段R|L.......R|L骨牌,进行批量处理。

func pushDominoes(dominoes string) string {
	arr := []byte(dominoes)
	i, n, left := 0, len(arr), byte('L')
	for i < n {
        // 跳过连续的LLRR段
        if arr[i] != '.' {
            left = arr[i]
            i++
            continue
        }
		j := i
		for j < n && arr[j] == '.' {
			j++
		}
        // leftright给个无副作用的初始值
		right := byte('R')
		if j < n {
			right = arr[j]
		}
        // 方向一致
		if left == right {
			for i < j {
				arr[i] = left
				i++
			}
            // 方向相对,注意当i=k时,受力平衡无须处理
		} else if left == 'R' && right == 'L' {
			k := j - 1
			for i < k {
				arr[i] = 'R'
				arr[k] = 'L'
				i++
				k--
			}
		}
		left = right
		i = j + 1
	}
	return string(arr)
}

除此之外,官方还使用了类似广度优先搜索来解决。

func pushDominoes(dominoes string) string {
    n := len(dominoes)
    q := []int{}
    // 记录当前位置受到了第几秒的推力
    time := make([]int, n)
    for i := range time {
        time[i] = -1  // 表示还没开始
    }
    force := make([][]byte, n) // 记录每个位置受到的推力数组,每个位置最多受到两个推力
    for i, ch := range dominoes {
        if ch != '.' {
            q = append(q, i)
            time[i] = 0  // 0表示最开始的推力
            force[i] = append(force[i], byte(ch))
        }
    }

    ans := bytes.Repeat([]byte{'.'}, n)
    for len(q) > 0 {
        i := q[0]
        q = q[1:]
        // 受到两个推力,无论时LR还是RL都最终对该位置无影响
        if len(force[i]) > 1 {
            continue
        }
        // 注意这里len(force[i]) == 1
        f := force[i][0]
        ans[i] = f
        // 判断该推力会对左边还是右边的骨牌产生影响
        ni := i - 1
        if f == 'R' {
            ni = i + 1
        }
        if 0 <= ni && ni < n {
            t := time[i]
            // 之前没有受到推力
            if time[ni] == -1 {
                q = append(q, ni)
                time[ni] = t + 1
                force[ni] = append(force[ni], f)
            // 判断是否是同一秒的推力
            } else if time[ni] == t+1 {
                force[ni] = append(force[ni], f)
            }
        }
    }
    return string(ans)
}