zoukankan      html  css  js  c++  java
  • [Leetcode 6] ZigZag问题的一种新思路

    原始问题(leetcode ZigZag Conversion

    假设固定一个行数(这里是3),字符串 "PAYPALISHIRING" 用“之”字形的方式能够写成:

    P   A   H   N
    A P L S I I G
    Y   I   R
    
     这个结果依照一行一行訪问的话是: "PAHNAPLSIIGYIR"。给定一个字符串以及一个表示行数的整数,实现一个函数,返回按行序拼接而成的新串。
    string convert(string text, int nRows);

    convert("PAYPALISHIRING", 3) 返回 "PAHNAPLSIIGYIR".

    思路

    假设把字母的排列看成一个二维的表格,那么最左边的那些字母无疑落在了第0列上。题目要求依照行号从小到大、同样行号从左到右的方式组成新字符串。典型方法有两个:第一,用一个二维数组来存放,依照ZigZag的方式一列一列填充数组。

    然后按行訪问就是新数组;第二,不使用额外空间。直接找出每一行不同字母在原字符串中的序号。leetcode上大多数方法应该都是这两种之中的一个或其变体。

    这里给出一个新思路,从还有一个角度来看问题。

    假设把每一个字母看成一个结点,每对相邻字母之间有条边。那么字符串就映射成了一张图。反过来看。对该图,原始序列事实上就是对该图进行深度优先遍历(Depth First Search)的结果。

    比方,对于字符串"ABCDE",ZigZag的排列方式例如以下,期望得到的新字符串为"AEBDC"。


    A   E
    B D
    C
    

    依照上述思路建立出下图。唯一不同的是多了个根结点R。R的出现是为了兴许的广度优先遍历(Breadth First Search)。

    第0行的每一个结点(如'A', 'E')都必须与R相连

    对这张图进行BFS,遍历序列为"RAEBDC",与期望结果相比仅仅在最左端多了一个R,能够为R赋一个空值""来解决。以下说到的虚拟结点也可採用同样技术。

    特殊情况

    在大功告成前还须要考虑一个情况。通过BFS得到目标字符串序列的前提,是每一个字母与源结点R有正确的距离。假设R的行号是-1。那么每一个字母所在的行号(假设从0開始)与R的距离就是距离源点R的深度。由于深度优先遍历是依照深度从小到大的顺序訪问结点。问题是E不一定存在。如原始串是"ABCD",相应的期望字符串为"ABDC"。

    但假设简单地把D与R相连的话就会产生"ADBC"的错误结果,这是由于D并不在第0行。一个方法是在最后一个处于非0行的字母与R之间建立一个或多个虚拟结点。形成一条虚拟路径。例如以下图所看到的。


    另一点须要注意的是。为了保证BFS的时候依照从左到右的顺序訪问邻居(如对于R来说。先"A"后"E"),在建图的时候也要按此顺序先后增加邻居(把"A"、"E"分别增加R中)。

    幸运的是,依照DFS顺序(即原始顺序)訪问结点能够满足该条件。

    Python代码

    from collections import deque
     
    class Node:
        def __init__(self, value):
            self.visited = False
            self.value = value
            self.neighbors = []
     
    class Solution:
        # @param {string} s
        # @param {integer} numRows
        # @return {string}
        def convert(self, s, numRows):
            self.__s = s
            self.__numRows = numRows
            return self.__BFS(self.__buildGraph())
     
        def __connect(self, prev, this):
            prev.neighbors.append(this)
            this.neighbors.append(prev)
     
        def __buildGraph(self):
            '''Build the graph from DFS traversal of the string'''
            root = Node('')
            prev = None
            row = 0
            up = True
            for i in range(len(self.__s)):
                this = Node(self.__s[i])
                if prev is not None:
                    self.__connect(prev, this)
                prev = this
                # Connect nearest nodes(those on row #0) to the root
                if row == 0:
                    self.__connect(root, this)
     
                if up:
                    if row < self.__numRows - 1:
                        row += 1
                    elif row > 0:
                        row -= 1
                        up = False
                else:
                    if row > 0:
                        row -= 1
                    elif row < self.__numRows - 1:
                        row += 1
                        up = True
     
            # The triky part, for BFS later
            # Build a virtual path to connect to the root for the last branch, if not already
            if not up and row < self.__numRows - 2:
                for i in range(row, -1, -1):
                    this = Node('')
                    self.__connect(prev, this)
                    prev = this
                self.__connect(prev, root)
     
            return root
     
     def __BFS(self, root):
        '''Breadth First Search gives the desired string'''
        work_queue = deque()
        root.visited = True
        work_queue.append(root)
     
        s = ''
        while work_queue:
            node = work_queue.popleft()
            # No special action for the root as it's an empty string;)
            s += node.value
            for i in node.neighbors:
                if not i.visited:
                i.visited = True
                work_queue.append(i)
        return s

    这个基于图的深度优先遍历的方法可能没有其它常规方法快(时间复杂度都是O(n),n为字符串长度)。但无疑是一种值得尝试的方法;) 以下给出两种easy想到的解法。

    常规方法之——“打表”法

    创建一个二维数组,以原字符串中的顺序从左到右一列一列填充。第一列从上往下填、第二列从下往上填,以此类推……填充完成后依照从上到下的顺序一行一行訪问就可以。

    class Solution:
        # @param {string} s
        # @param {integer} numRows
        # @return {string}
        def convert(self, s, numRows):
            res = ''
            # If numRows is 0, the step should be 1
            step = max((numRows - 1) * 2, 1)
            # Exit if numRows is larger
            for line in range(min(numRows, len(s))):
                if line == 0 or line == numRows - 1:
                    step1 = step2 = step
                else:
                    step1 = step - line * 2
                    step2 = step - step1
     
                j = line
                while j < len(s):
                    res += s[j]
                    j += step1
                    step1, step2 = step2, step1
            return res

    常规方法之——找规律法

    要是知道某一行某一列的字母在原串中的序号就好了。在样例"ABCDE"中,第二行第一列的元素在原字符串中的下标一定是1(从0開始),因此是"B"。这里就不给出分析过程了,详细參考以下代码。在纸上画10个结点。行数为3。从0開始标记。不难找出同一行上相邻结点间隔的规律(及以下的step)。

    class Solution:
        # @param {string} s
        # @param {integer} numRows
        # @return {string}
        def convert(self, s, numRows):
            table = [[] for i in range(numRows)]
            line_in_table = 0
            up = True
            for i in s:
                table[line_in_table].append(i)
                if up:
                    if line_in_table < numRows - 1:
                        line_in_table += 1
                    else:
                        line_in_table -= 1
                        up = False
                else:
                    if line_in_table > 0:
                        line_in_table -= 1
                    else:
                        line_in_table += 1
                        up = True
     
            res = ''
            for row in table:
                for col in row:
                    res += col
            return res

    小结

    他山之石,能够攻玉。

    多想想问题。会有不同的启示!

  • 相关阅读:
    Java多线程编程核心技术---对象及变量的并发访问(二)
    Java多线程编程核心技术---对象及变量的并发访问(一)
    Java多线程编程核心技术---Java多线程技能
    普通浏览器GET请求与Ajax的GET请求的区别
    【python】-- 面向对象引子、概念
    【python】-- json & pickle、xml、requests、hashlib、shelve、shutil、configparser、subprocess
    【python】-- 内置函数、软件目录开发规范(代码编码风格)
    【python】-- 装饰器、迭代器、生成器
    【python】-- 递归函数、高阶函数、嵌套函数、匿名函数
    【python】-- 函数非固定参数,返回值(return)
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7020246.html
Copyright © 2011-2022 走看看