楼主: zhuli19901106
跳转到指定楼层
上一主题 下一主题
收起左侧

分享我的Lintcode题解,目前进度244/248

 
🔗
 楼主| zhuli19901106 2015-7-26 00:21:55 | 只看该作者
全局:
Count and Say
题意:给定一个序列,把它”读出来“。比如1123读作211213,看出规律了吧?就是数个数。如果按照起始序列为”1“,每次都进行这种变换,求第N个序列。
解法:因为结果是固定的,所以算完就保存下来,免得下次又算。所谓动态打表嘛。
代码:
  1. #include <unordered_map>
  2. using namespace std;

  3. class Solution {
  4. public:
  5.     /**
  6.      * @param n the nth
  7.      * @return the nth sequence
  8.      */
  9.     Solution() {
  10.         ans[1] = "1";
  11.         max_n = 1;
  12.     }
  13.    
  14.     string countAndSay(int n) {
  15.         if (ans.find(n) != ans.end()) {
  16.             return ans[n];
  17.         }
  18.         int i;
  19.         for (i = max_n; i < n; ++i) {
  20.             ans[i + 1] = next(ans[i]);
  21.         }
  22.         max_n = n;
  23.         return ans[n];
  24.     }
  25.    
  26.     ~Solution() {
  27.         ans.clear();
  28.     }
  29. private:
  30.     unordered_map<int, string> ans;
  31.     int max_n;
  32.    
  33.     string next(string s) {
  34.         string s1;
  35.         int n, i, j;
  36.         
  37.         n = s.length();
  38.         i = 0;
  39.         s1 = "";
  40.         while (i < n) {
  41.             j = i;
  42.             while (j < n && s[i] == s[j]) {
  43.                 ++j;
  44.             }
  45.             s1 += to_string(j - i);
  46.             s1.push_back(s[i]);
  47.             i = j;
  48.         }
  49.         return s1;
  50.     }
  51. };
复制代码
复杂度:时间O(N ^ 2),空间O(N)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 00:27:29 | 只看该作者
全局:
本帖最后由 zhuli19901106 于 2015-7-26 00:33 编辑

Kth Smallest Number in Sorted Matrix
题意:给定一个杨氏矩阵,求第K小的数。
解法:用小顶堆。把N行当成N个有序链表,这题就等效于Merge K Sorted List。上次在一个讨论贴里看到有位老兄说这题可以做到O(K)时间,好像是mgccl说的。
代码:
  1. #include <queue>
  2. using namespace std;

  3. vector<vector<int> > *pa;
  4. int n, m;

  5. typedef struct Comp {
  6.     bool operator () (const int &i1, const int &i2) const {
  7.         return (*pa)[i1 / m][i1 % m] > (*pa)[i2 / m][i2 % m];
  8.     }
  9. } Comp;
  10. priority_queue<int, vector<int>, Comp> pq;

  11. class Solution {
  12. public:
  13.     /**
  14.      * @param matrix: a matrix of integers
  15.      * @param k: an integer
  16.      * @return: the kth smallest number in the matrix
  17.      */
  18.     int kthSmallest(vector<vector<int> > &matrix, int k) {
  19.         vector<vector<int> > &a = matrix;
  20.         n = a.size();
  21.         m = a[0].size();
  22.         
  23.         pa = &matrix;
  24.         int i, j;
  25.         for (i = 0; i < n; ++i) {
  26.             pq.push(i * m);
  27.         }
  28.         
  29.         int x, y, c;
  30.         int ans;
  31.         for (i = 0; i < k; ++i) {
  32.             c = pq.top();
  33.             pq.pop();
  34.             x = c / m;
  35.             y = c % m;
  36.             ans = a[x][y];
  37.             if (y < m - 1) {
  38.                 ++y;
  39.                 pq.push(x * m + y);
  40.             }
  41.         }
  42.         
  43.         while (!pq.empty()) {
  44.             pq.pop();
  45.         }
  46.         return ans;
  47.     }
  48. };
复制代码
复杂度:时间O(K * log(N)),空间O(N)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 00:52:31 | 只看该作者
全局:
本帖最后由 zhuli19901106 于 2015-7-26 00:53 编辑

Maximum Gap
题意:hard难度。给定一个无序的数组,如果把它排序,请求出排好序以后相邻元素的最大差值。
解法1:排序。
代码1:
  1. // The straight-forward solution
  2. #include <algorithm>
  3. using namespace std;

  4. class Solution {
  5. public:
  6.     /**
  7.      * @param nums: a vector of integers
  8.      * @return: the maximum difference
  9.      */
  10.     int maximumGap(vector<int> nums) {
  11.         sort(nums.begin(), nums.end());
  12.         int ans = 0;
  13.         int n = nums.size();
  14.         int i;
  15.         for (i = 1; i < n; ++i) {
  16.             ans = max(ans, nums[i] - nums[i - 1]);
  17.         }
  18.         return ans;
  19.     }
  20. };
复制代码
复杂度1:时间O(N * log(N)),空间O(1)。

解法2:这题确实挺难想的。排序的做法就不提了,说说不排序的。我独立想了四十分钟没有思路,然后上网搜,结果看到了”桶排序“后就大概明白了,然后搞出了下面这个解法。在这儿就不细讲了,要写清楚篇幅太大。桶排序这个点子我是真没想到。
代码2:
  1. // O(n) solution, yet tricky and slow...
  2. #include <climits>
  3. #include <unordered_set>
  4. using namespace std;

  5. class Solution {
  6. public:
  7.     /**
  8.      * @param nums: a vector of integers
  9.      * @return: the maximum difference
  10.      */
  11.     int maximumGap(vector<int> nums) {
  12.         vector<int> a;
  13.         unordered_set<int> us;
  14.         int n = nums.size();
  15.         int i;
  16.         for (i = 0; i < n; ++i) {
  17.             us.insert(nums[i]);
  18.         }
  19.         for (auto it = us.begin(); it != us.end(); ++it) {
  20.             a.push_back(*it);
  21.         }
  22.         
  23.         n = a.size();
  24.         if (n < 2) {
  25.             return 0;
  26.         }
  27.         int minVal = INT_MAX;
  28.         for (i = 0; i < n; ++i) {
  29.             minVal = min(minVal, a[i]);
  30.         }
  31.         int maxVal = INT_MIN;
  32.         for (i = 0; i < n; ++i) {
  33.             a[i] -= minVal;
  34.             maxVal = max(maxVal, a[i]);
  35.         }
  36.         int d = maxVal / (n - 1);
  37.         vector<vector<int> > b(n);
  38.         for (i = 0; i < n; ++i) {
  39.             if (b[a[i] / d].empty()) {
  40.                 b[a[i] / d].push_back(a[i]);
  41.                 b[a[i] / d].push_back(a[i]);
  42.             } else {
  43.                 b[a[i] / d][0] = min(b[a[i] / d][0], a[i]);
  44.                 b[a[i] / d][1] = max(b[a[i] / d][1], a[i]);
  45.             }
  46.         }
  47.         i = 0;
  48.         int j;
  49.         int ans = 0;
  50.         while (i < n) {
  51.             j = i + 1;
  52.             while (j < n && b[j].empty()) {
  53.                 ++j;
  54.             }
  55.             if (j == n) {
  56.                 break;
  57.             }
  58.             ans = max(ans, b[j][0] - b[i][1]);
  59.             i = j;
  60.         }
  61.         return ans;
  62.     }
  63. };
复制代码
复杂度2:时间O(N),空间O(N)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 01:05:21 | 只看该作者
全局:
Simplify Path
题意:给定UNIX风格的路径,把其中存在"."、”..“和”///“之类的给简化掉,给出最简路径。
解法:正则处理字符串。用栈记录路径。
代码:
  1. import re

  2. class Solution:
  3.     # @param {string} path the original path
  4.     # @return {string} the simplified path
  5.     def simplifyPath(self, path):
  6.         path = re.split('/+', path.strip('/ \n\t'))
  7.         st = []
  8.         for token in path:
  9.             if token == '.':
  10.                 continue
  11.             elif token == '..':
  12.                 if len(st) > 0:
  13.                     st.pop()
  14.             else:
  15.                 st.append(token)
  16.         return '/' + '/'.join(st)
复制代码
复杂度:时间O(N),空间O(N)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 01:07:46 | 只看该作者
全局:
Length of Last Word
题意:给定一个字符串,求最后一个单词的长度。
解法:注意处理空格即可。
代码:
  1. class Solution {
  2. public:
  3.     /**
  4.      * @param s A string
  5.      * @return the length of last word
  6.      */
  7.     int lengthOfLastWord(string &s) {
  8.         int i, j;
  9.         
  10.         j = s.length() - 1;
  11.         while (j >= 0 && s[j] == ' ') {
  12.             --j;
  13.         }
  14.         i = j;
  15.         while (i >= 0 && s[i] != ' ') {
  16.             --i;
  17.         }
  18.         return j - i;
  19.     }
  20. };
复制代码
复杂度:时间O(N),空间O(1)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 01:09:50 | 只看该作者
全局:
Valid Parentheses
题意:括号序列匹配,可能包含大中小括号。
解法:用栈。
代码:
  1. #include <map>
  2. #include <stack>
  3. using namespace std;

  4. class Solution {
  5. public:
  6.     Solution() {
  7.         m[')'] = '(';
  8.         m[']'] = '[';
  9.         m['}'] = '{';
  10.     }
  11.     /**
  12.      * @param s A string
  13.      * @return whether the string is a valid parentheses
  14.      */
  15.     bool isValidParentheses(string& s) {
  16.         int n = s.length();
  17.         stack<char> st;
  18.         
  19.         int i;
  20.         for (i = 0; i < n; ++i) {
  21.             switch (s[i]) {
  22.             case '(':
  23.             case '[':
  24.             case '{':
  25.                 st.push(s[i]);
  26.                 break;
  27.             case ')':
  28.             case ']':
  29.             case '}':
  30.                 if (st.empty() || st.top() != m[s[i]]) {
  31.                     return false;
  32.                 }
  33.                 st.pop();
  34.                 break;
  35.             }
  36.         }
  37.         return st.empty();
  38.     }
  39. private:
  40.     map<char, char> m;
  41. };
复制代码
复杂度:时间O(N),空间O(N)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 01:12:44 | 只看该作者
全局:
Evaluate Reverse Polish Notation
题意:逆波兰表达式求值。
解法:无需考虑优先级问题,直接用栈处理即可。
代码:
  1. import re

  2. class Solution:
  3.     op = {}
  4.    
  5.     def sign(self, x):
  6.         return 0 if x == 0 else 1 if x > 0 else -1
  7.         
  8.     def __init__(self):
  9.         self.op['+'] = lambda x, y: x + y
  10.         self.op['-'] = lambda x, y: x - y
  11.         self.op['*'] = lambda x, y: x * y
  12.         self.op['/'] = lambda x, y: abs(x) / abs(y) * self.sign(x * y)
  13.         
  14.     # @param {string[]} tokens The Reverse Polish Notation
  15.     # @return {int} the value
  16.     def evalRPN(self, tokens):
  17.         st = []
  18.         for token in tokens:
  19.             if re.match(r'[\+\-]?\d+', token):
  20.                 st.append(int(token))
  21.             else:
  22.                 n2 = st.pop()
  23.                 n1 = st.pop()
  24.                 st.append(self.op[token](n1, n2))
  25.         return st.pop()
  26.         
复制代码
复杂度:时间O(N),空间O(N)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 01:15:30 | 只看该作者
全局:
Continuous Subarray Sum
题意:最大子数组和。求出起点和终点。
解法:对O(N)的算法进行适当修改,就可以记录下起点和终点。
代码:
  1. class Solution {
  2. public:
  3.     /**
  4.      * @param A an integer array
  5.      * @return  A list of integers includes the index of
  6.      *          the first number and the index of the last number
  7.      */
  8.     vector<int> continuousSubarraySum(vector<int>& A) {
  9.         int n = A.size();
  10.         int ll;
  11.         int sum;
  12.         int msum;
  13.         
  14.         ll = 0;
  15.         msum = A[0];
  16.         int i;
  17.         for (i = 1; i < n; ++i) {
  18.             if (A[i] > msum) {
  19.                 msum = A[i];
  20.                 ll = i;
  21.             }
  22.         }
  23.         vector<int> ans;
  24.         if (msum <= 0) {
  25.             ans.push_back(ll);
  26.             ans.push_back(ll);
  27.             return ans;
  28.         }
  29.         
  30.         int mll, mrr;
  31.         ll = 0;
  32.         msum = sum = 0;
  33.         for (i = 0; i < n; ++i) {
  34.             sum += A[i];
  35.             if (sum < 0) {
  36.                 sum = 0;
  37.                 ll = i + 1;
  38.             }
  39.             if (sum > msum) {
  40.                 msum = sum;
  41.                 mll = ll;
  42.                 mrr = i;
  43.             }
  44.         }
  45.         ans.push_back(mll);
  46.         ans.push_back(mrr);
  47.         return ans;
  48.     }
  49. };
复制代码
复杂度:时间O(N),空间O(1)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 02:16:26 | 只看该作者
全局:
Continuous Subarray Sum II
题意:最大子数组和,求出起点和终点。不过这次允许”循环子数组“,也就是从尾到头的子数组。
解法1:分情况处理,一种是连续子数组,一种是两段子数组。代码写得很繁琐,思路不清晰。
代码1:
  1. class Solution {
  2. public:
  3.     /**
  4.      * @param A an integer array
  5.      * @return  A list of integers includes the index of
  6.      *          the first number and the index of the last number
  7.      */
  8.     vector<int> continuousSubarraySumII(vector<int>& A) {
  9.         int msum1;
  10.         vector<int> ans1;
  11.         ans1 = continuousSubarraySum(A, msum1);
  12.         if (ans1[0] == ans1[1]) {
  13.             return ans1;
  14.         }
  15.         
  16.         vector<int> s1;
  17.         vector<int> i1;
  18.         vector<int> s2;
  19.         vector<int> i2;
  20.         
  21.         int n = A.size();
  22.         int i;
  23.         int sum;
  24.         
  25.         s1.resize(n);
  26.         i1.resize(n);
  27.         s2.resize(n);
  28.         i2.resize(n);
  29.         
  30.         sum = A[0];
  31.         s1[0] = A[0];
  32.         i1[0] = 0;
  33.         for (i = 1; i <= n - 1; ++i) {
  34.             sum += A[i];
  35.             if (sum > s1[i - 1]) {
  36.                 s1[i] = sum;
  37.                 i1[i] = i;
  38.             } else {
  39.                 s1[i] = s1[i - 1];
  40.                 i1[i] = i1[i - 1];
  41.             }
  42.         }
  43.         
  44.         sum = A[n - 1];
  45.         s2[n - 1] = A[n - 1];
  46.         i2[n - 1] = n - 1;
  47.         for (i = n - 2; i >= 0; --i) {
  48.             sum += A[i];
  49.             if (sum > s2[i + 1]) {
  50.                 s2[i] = sum;
  51.                 i2[i] = i;
  52.             } else {
  53.                 s2[i] = s2[i + 1];
  54.                 i2[i] = i2[i + 1];
  55.             }
  56.         }
  57.         
  58.         int msum2 = 0;
  59.         vector<int> ans2;
  60.         ans2.resize(2);
  61.         for (i = 0; i < n - 1; ++i) {
  62.             if (s1[i] + s2[i + 1] > msum2) {
  63.                 msum2 = s1[i] + s2[i + 1];
  64.                 ans2[0] = i2[i + 1];
  65.                 ans2[1] = i1[i];
  66.             }
  67.         }
  68.         return msum1 > msum2 ? ans1 : ans2;
  69.     }
  70. private:
  71.     vector<int> continuousSubarraySum(vector<int>& A, int &msum) {
  72.         int n = A.size();
  73.         int ll;
  74.         int sum;
  75.         
  76.         ll = 0;
  77.         msum = A[0];
  78.         int i;
  79.         for (i = 1; i < n; ++i) {
  80.             if (A[i] > msum) {
  81.                 msum = A[i];
  82.                 ll = i;
  83.             }
  84.         }
  85.         vector<int> ans;
  86.         if (msum <= 0) {
  87.             ans.push_back(ll);
  88.             ans.push_back(ll);
  89.             return ans;
  90.         }
  91.         
  92.         int mll, mrr;
  93.         ll = 0;
  94.         msum = sum = 0;
  95.         for (i = 0; i < n; ++i) {
  96.             sum += A[i];
  97.             if (sum < 0) {
  98.                 sum = 0;
  99.                 ll = i + 1;
  100.             }
  101.             if (sum > msum) {
  102.                 msum = sum;
  103.                 mll = ll;
  104.                 mrr = i;
  105.             }
  106.         }
  107.         ans.push_back(mll);
  108.         ans.push_back(mrr);
  109.         return ans;
  110.     }
  111. };
复制代码
复杂度1:时间O(N),空间O(N)。

解法2:对于两段子数组,其实可以转而关注中间的那段没被选取的子数组。于是,求出了中间那段的最小值,也就求出了两段的最大和,于是有了下面解法。代码相对来说好写多了。
代码2:
  1. #include <algorithm>
  2. using namespace std;

  3. class Solution {
  4. public:
  5.     /**
  6.      * @param A an integer array
  7.      * @return  A list of integers includes the index of
  8.      *          the first number and the index of the last number
  9.      */
  10.     vector<int> continuousSubarraySumII(vector<int> &A) {
  11.         vector<int> pos1(2), pos2(2);
  12.         int msum1, msum2;
  13.         int n = A.size();
  14.         int i;
  15.         
  16.         pos1 = continuousSubarraySum(A, msum1);
  17.         if (msum1 <= 0) {
  18.             return pos1;
  19.         }
  20.         
  21.         int sum = 0;
  22.         for (i = 0; i < n; ++i) {
  23.             sum += A[i];
  24.             A[i] = -A[i];
  25.         }
  26.         pos2 = continuousSubarraySum(A, msum2);
  27.         msum2 = sum + msum2;
  28.         
  29.         pos2[0] = (pos2[0] + n - 1) % n;
  30.         pos2[1] = (pos2[1] + 1) % n;
  31.         swap(pos2[0], pos2[1]);
  32.         
  33.         return msum1 >= msum2 ? pos1 : pos2;
  34.     }
  35. private:
  36.     vector<int> continuousSubarraySum(vector<int>& A, int &msum) {
  37.         int n = A.size();
  38.         int ll;
  39.         int sum;
  40.         
  41.         ll = 0;
  42.         msum = A[0];
  43.         int i;
  44.         for (i = 1; i < n; ++i) {
  45.             if (A[i] > msum) {
  46.                 msum = A[i];
  47.                 ll = i;
  48.             }
  49.         }
  50.         vector<int> ans;
  51.         if (msum <= 0) {
  52.             ans.push_back(ll);
  53.             ans.push_back(ll);
  54.             return ans;
  55.         }
  56.         
  57.         int mll, mrr;
  58.         ll = 0;
  59.         msum = sum = 0;
  60.         for (i = 0; i < n; ++i) {
  61.             sum += A[i];
  62.             if (sum < 0) {
  63.                 sum = 0;
  64.                 ll = i + 1;
  65.             }
  66.             if (sum > msum) {
  67.                 msum = sum;
  68.                 mll = ll;
  69.                 mrr = i;
  70.             }
  71.         }
  72.         ans.push_back(mll);
  73.         ans.push_back(mrr);
  74.         return ans;
  75.     }
  76. };
复制代码
复杂度2:时间O(N),空间O(1)。
回复

使用道具 举报

🔗
 楼主| zhuli19901106 2015-7-26 02:29:20 | 只看该作者
全局:
Subarray Sum II
题意:hard难度。给定一个整数,和两个值start和end。求出和介于这两者之间的所有的子数组的个数。
解法1:暴力枚举。这题没有负数。
代码1:
  1. // O(n ^ 2) solution
  2. // There is no negative number?
  3. #include <algorithm>
  4. using namespace std;

  5. class Solution {
  6. public:
  7.     /**
  8.      * @param A an integer array
  9.      * @param start an integer
  10.      * @param end an integer
  11.      * @return the number of possible answer
  12.      */
  13.     int subarraySumII(vector<int> &A, int start, int end) {
  14.         if (start == end) {
  15.             return 0;
  16.             return 0;
  17.         }
  18.         
  19.         vector<int> &a = A;
  20.         int n = a.size();
  21.         vector<int> s(n + 1, 0);
  22.         int i;
  23.         for (i = 0; i < n; ++i) {
  24.             s[i + 1] = s[i] + a[i];
  25.         }
  26.         // sort(s.begin(), s.end());
  27.         int ans = 0;
  28.         int j;
  29.         for (i = 0; i < n; ++i) {
  30.             for (j = i + 1; j <= n; ++j) {
  31.                 if (s[j] - s[i] >= start && s[j] - s[i] <= end) {
  32.                     ++ans;
  33.                 }
  34.             }
  35.         }
  36.         return ans;
  37.     }
  38. };
复制代码
复杂度1:时间O(N ^ 2),空间O(N)。

解法2:第一维枚举,第二维二分。
代码2:
  1. // O(n * log(n)) solution
  2. // So, there is no negative number.
  3. #include <algorithm>
  4. using namespace std;

  5. class Solution {
  6. public:
  7.     /**
  8.      * @param A an integer array
  9.      * @param start an integer
  10.      * @param end an integer
  11.      * @return the number of possible answer
  12.      */
  13.     int subarraySumII(vector<int> &A, int start, int end) {
  14.         if (start == end) {
  15.             return 0;
  16.             return 0;
  17.         }
  18.         
  19.         vector<int> &a = A;
  20.         int n = a.size();
  21.         vector<int> s(n + 1, 0);
  22.         int i;
  23.         for (i = 0; i < n; ++i) {
  24.             s[i + 1] = s[i] + a[i];
  25.         }
  26.         sort(s.begin(), s.end());
  27.         int ans = 0;
  28.         int j1, j2;
  29.         for (i = 0; i < n; ++i) {
  30.             j1 = lower_bound(s.begin() + i, s.end(), start + s[i]) - s.begin();
  31.             j2 = upper_bound(s.begin() + i, s.end(), end + s[i]) - s.begin();
  32.             ans += j2 - j1;
  33.         }
  34.         return ans;
  35.     }
  36. };
复制代码
复杂度2:时间O(N * log(N)),空间O(N)。

解法3:可以做到线性时间。
代码3:
  1. // O(n) solution
  2. class Solution {
  3. public:
  4.     /**
  5.      * @param A an integer array
  6.      * @param start an integer
  7.      * @param end an integer
  8.      * @return the number of possible answer
  9.      */
  10.     int subarraySumII(vector<int> &A, int start, int end) {
  11.         int n = A.size();
  12.         if (n == 0) {
  13.             return 0;
  14.         }
  15.         int i, j, k;
  16.         j = k = 0;
  17.         vector<int> S(n + 1, 0);
  18.         for (i = 0; i < n; ++i) {
  19.             S[i + 1] = S[i] + A[i];
  20.         }
  21.         
  22.         int ans = 0;
  23.         j = k = 1;
  24.         for (i = 0; i < n; ++i) {
  25.             while (j <= n && S[j] - S[i] < start) {
  26.                 ++j;
  27.             }
  28.             while (k <= n && S[k] - S[i] <= end) {
  29.                 ++k;
  30.             }
  31.             ans += k - j;
  32.         }
  33.         return ans;
  34.     }
  35. };
复制代码
复杂度3:时间O(N),空间O(N)。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表